/* * 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 (c) 1987-1993 The Regents of the University of California. * Copyright (c) 1993-1997 Lucent Technologies. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * Copyright (c) 1998-1999 by Scriptics Corporation. * Copyright (c) 2001 by Kevin B. Kenny. All rights reserved. * Copyright (c) 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. * * RCS: @(#) $Id: tclCmdIL.c,v 1.160 2008/10/07 17:57:43 msofer Exp $ */ #include "tclInt.h" #include "tclRegexp.h" /* * 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 { char *strValuePtr; long intValue; double doubleValue; Tcl_Obj *objValuePtr; } index; Tcl_Obj *objPtr; /* Object being sorted, or its index. */ 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 *, size_t); /* * 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 SortInfo { 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. Pre-initialized to hold * base of command. */ int *indexv; /* If the -index option was specified, this * holds 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. */ int 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 /* * Magic values for the index field of the SortInfo structure. Note that the * index "end-1" will be translated to SORTIDX_END-1, etc. */ #define SORTIDX_NONE -1 /* Not indexed; use whole value. */ #define SORTIDX_END -2 /* Indexed from end. */ /* * Forward declarations for procedures defined in this file: */ static int DictionaryCompare(char *left, char *right); static int InfoArgsCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoBodyCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoCmdCountCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoCommandsCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoCompleteCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoDefaultCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); /* TIP #280 - New 'info' subcommand 'frame' */ static int InfoFrameCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoFunctionsCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoHostnameCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoLevelCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoLibraryCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoLoadedCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoNameOfExecutableCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoPatchLevelCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoProcsCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoScriptCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoSharedlibCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int InfoTclVersionCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); 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, NULL}, {"body", InfoBodyCmd, NULL}, {"cmdcount", InfoCmdCountCmd, NULL}, {"commands", InfoCommandsCmd, NULL}, {"complete", InfoCompleteCmd, NULL}, {"coroutine", TclInfoCoroutineCmd, NULL}, {"default", InfoDefaultCmd, NULL}, {"exists", TclInfoExistsCmd, TclCompileInfoExistsCmd}, {"frame", InfoFrameCmd, NULL}, {"functions", InfoFunctionsCmd, NULL}, {"globals", TclInfoGlobalsCmd, NULL}, {"hostname", InfoHostnameCmd, NULL}, {"level", InfoLevelCmd, NULL}, {"library", InfoLibraryCmd, NULL}, {"loaded", InfoLoadedCmd, NULL}, {"locals", TclInfoLocalsCmd, NULL}, {"nameofexecutable", InfoNameOfExecutableCmd, NULL}, {"patchlevel", InfoPatchLevelCmd, NULL}, {"procs", InfoProcsCmd, NULL}, {"script", InfoScriptCmd, NULL}, {"sharedlibextension", InfoSharedlibCmd, NULL}, {"tclversion", InfoTclVersionCmd, NULL}, {"vars", TclInfoVarsCmd, NULL}, {NULL, NULL, NULL} }; /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRIfObjCmd, dummy, objc, objv); } int TclNRIfObjCmd( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int thenScriptIndex = 0; /* "then" script to be evaled after syntax * check. */ Interp *iPtr = (Interp *) interp; int i, result, value; char *clause; i = 1; while (1) { /* * 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) { clause = TclGetString(objv[i-1]); Tcl_AppendResult(interp, "wrong # args: ", "no expression after \"", clause, "\" argument", NULL); return TCL_ERROR; } if (!thenScriptIndex) { result = Tcl_ExprBooleanObj(interp, objv[i], &value); if (result != TCL_OK) { return result; } } i++; if (i >= objc) { missingScript: clause = TclGetString(objv[i-1]); Tcl_AppendResult(interp, "wrong # args: ", "no script following \"", clause, "\" argument", NULL); return TCL_ERROR; } 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)) { i++; continue; } break; } /* * 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) { Tcl_AppendResult(interp, "wrong # args: ", "no script following \"else\" argument", NULL); return TCL_ERROR; } } if (i < objc - 1) { Tcl_AppendResult(interp, "wrong # args: ", "extra words after \"else\" clause in \"if\" command", 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); } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ 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 { incrPtr = Tcl_NewIntObj(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: * FIXME * * 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { register Interp *iPtr = (Interp *) interp; 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_AppendResult(interp, "\"", name, "\" isn't a procedure", 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { register Interp *iPtr = (Interp *) interp; char *name; Proc *procPtr; Tcl_Obj *bodyPtr, *resultPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "procname"); return TCL_ERROR; } name = TclGetString(objv[1]); procPtr = TclFindProc(iPtr, name); if (procPtr == NULL) { Tcl_AppendResult(interp, "\"", name, "\" isn't a procedure", 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. */ bodyPtr = procPtr->bodyPtr; if (bodyPtr->bytes == NULL) { /* * The string rep might not be valid if the procedure has never been * run before. [Bug #545644] */ TclGetString(bodyPtr); } resultPtr = Tcl_NewStringObj(bodyPtr->bytes, bodyPtr->length); Tcl_SetObjResult(interp, resultPtr); 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( ClientData dummy, /* Not used. */ 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_NewIntObj(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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { char *cmdName, *pattern; const char *simplePattern; register 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; int 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, (Namespace *) 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_GetHashValue(entryPtr); elemObjPtr = Tcl_NewObj(); Tcl_GetCommandFullName(interp, cmd, elemObjPtr); } else { cmdName = 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 = 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 = Tcl_GetHashKey(&nsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { if (specificNsInPattern) { cmd = Tcl_GetHashValue(entryPtr); elemObjPtr = Tcl_NewObj(); 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 = 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 = 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, (char *)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 = Tcl_GetHashKey(&pathNsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { elemObjPtr = Tcl_NewStringObj(cmdName, -1); (void) Tcl_CreateHashEntry(&addedCommandsTable, (char *) 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 = Tcl_GetHashKey(&globalNsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { elemObjPtr = Tcl_NewStringObj(cmdName, -1); if (Tcl_FindHashEntry(&addedCommandsTable, (char *) 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( ClientData dummy, /* Not used. */ 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; char *procName, *argName, *varName; 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_AppendResult(interp, "\"", procName, "\" isn't a procedure",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, 0); if (valueObjPtr == NULL) { goto defStoreError; } Tcl_SetObjResult(interp, Tcl_NewIntObj(1)); } else { Tcl_Obj *nullObjPtr = Tcl_NewObj(); valueObjPtr = Tcl_ObjSetVar2(interp, objv[3], NULL, nullObjPtr, 0); if (valueObjPtr == NULL) { goto defStoreError; } Tcl_SetObjResult(interp, Tcl_NewIntObj(0)); } return TCL_OK; } } Tcl_AppendResult(interp, "procedure \"", procName, "\" doesn't have an argument \"", argName, "\"", NULL); return TCL_ERROR; defStoreError: varName = TclGetString(objv[3]); Tcl_AppendResult(interp, "couldn't store default value in variable \"", varName, "\"", NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; int level, topLevel; CmdFrame *framePtr; topLevel = ((iPtr->cmdFramePtr == NULL) ? 0 : iPtr->cmdFramePtr->level); if (iPtr->execEnvPtr->corPtr) { /* * A coroutine: must fix the level computations */ topLevel += iPtr->execEnvPtr->corPtr->caller.cmdFramePtr->level + 1 - iPtr->execEnvPtr->corPtr->base.cmdFramePtr->level; } if (objc == 1) { /* * Just "info frame". */ Tcl_SetObjResult(interp, Tcl_NewIntObj (topLevel)); return TCL_OK; } else if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "?number?"); return TCL_ERROR; } /* * We've got "info frame level" and must parse the level first. */ if (TclGetIntFromObj(interp, objv[1], &level) != TCL_OK) { return TCL_ERROR; } if ((level > topLevel) || (level <= - topLevel)) { levelError: Tcl_AppendResult(interp, "bad level \"", TclGetString(objv[1]), "\"", NULL); return TCL_ERROR; } /* * 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)); return TCL_OK; } /* *---------------------------------------------------------------------- * * 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 *lv[20]; /* 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 *typeString[TCL_LOCATION_LAST] = { "eval", "eval", "eval", "precompiled", "source", "proc" }; Tcl_Obj *tmpObj; Proc *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)); ADD_PAIR("line", Tcl_NewIntObj(framePtr->line[0])); ADD_PAIR("cmd", Tcl_NewStringObj(framePtr->cmd.str.cmd, framePtr->cmd.str.len)); break; case TCL_LOCATION_EVAL_LIST: /* * List optimized evaluation. Type, line, cmd, the latter through * listPtr, possibly a frame. */ ADD_PAIR("type", Tcl_NewStringObj(typeString[framePtr->type], -1)); ADD_PAIR("line", Tcl_NewIntObj(1)); /* * We put a duplicate of the command list obj into the result to * ensure that the 'pure List'-property of the command itself is not * destroyed. Otherwise the query here would disable the list * optimization path in Tcl_EvalObjEx. */ ADD_PAIR("cmd", Tcl_DuplicateObj(framePtr->cmd.listPtr)); break; case TCL_LOCATION_PREBC: /* * Precompiled. Result contains the type as signal, nothing else. */ ADD_PAIR("type", Tcl_NewStringObj(typeString[framePtr->type], -1)); break; case TCL_LOCATION_BC: { /* * Execution of bytecode. Talk to the BC engine to fill out the frame. */ CmdFrame *fPtr; 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_NewIntObj(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", Tcl_NewStringObj(fPtr->cmd.str.cmd, fPtr->cmd.str.len)); 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_NewIntObj(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", Tcl_NewStringObj(framePtr->cmd.str.cmd, framePtr->cmd.str.len)); 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) { /* * This is a regular command. */ char *procName = Tcl_GetHashKey(namePtr->tablePtr, namePtr); char *nsName = procPtr->cmdPtr->nsPtr->fullName; ADD_PAIR("proc", Tcl_NewStringObj(nsName, -1)); if (strcmp(nsName, "::") != 0) { Tcl_AppendToObj(lv[lc-1], "::", -1); } Tcl_AppendToObj(lv[lc-1], procName, -1); } else if (procPtr->cmdPtr->clientData) { ExtraFrameInfo *efiPtr = procPtr->cmdPtr->clientData; int 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++] = efiPtr->fields[i].clientData; } } } } /* * 'level'. Common to all frame types. Conditional on having an associated * _visible_ CallFrame. */ if ((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_NewIntObj(t - c)); break; } } } return Tcl_NewListObj(lc, lv); } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { char *pattern; if (objc == 1) { pattern = NULL; } else if (objc == 2) { pattern = TclGetString(objv[1]); } else { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_ListMathFuncs(interp, pattern)); return TCL_OK; } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ 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_SetResult(interp, "unable to determine name of host", TCL_STATIC); 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( ClientData dummy, /* Not used. */ 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_NewIntObj(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 (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_AppendResult(interp, "bad level \"", TclGetString(objv[1]), "\"", 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( ClientData dummy, /* Not used. */ 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_GetVar(interp, "tcl_library", TCL_GLOBAL_ONLY); if (libDirName != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(libDirName, -1)); return TCL_OK; } Tcl_SetResult(interp, "no library has been specified for Tcl",TCL_STATIC); 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { char *interpName; int result; if ((objc != 1) && (objc != 2)) { Tcl_WrongNumArgs(interp, 1, objv, "?interp?"); return TCL_ERROR; } if (objc == 1) { /* Get loaded pkgs in all interpreters. */ interpName = NULL; } else { /* Get pkgs just in specified interp. */ interpName = TclGetString(objv[1]); } result = TclGetLoadedPackages(interp, interpName); return result; } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ 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( ClientData dummy, /* Not used. */ 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_GetVar(interp, "tcl_patchLevel", (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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { char *cmdName, *pattern; const char *simplePattern; Namespace *nsPtr; #ifdef INFO_PROCS_SEARCH_GLOBAL_NS Namespace *globalNsPtr = (Namespace *) Tcl_GetGlobalNamespace(interp); #endif Namespace *currNsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp); Tcl_Obj *listPtr, *elemObjPtr; int specificNsInPattern = 0;/* Init. to avoid compiler warning. */ register 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, (Namespace *) 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); #ifndef INFO_PROCS_SEARCH_GLOBAL_NS if (simplePattern != NULL && TclMatchIsTrivial(simplePattern)) { entryPtr = Tcl_FindHashEntry(&nsPtr->cmdTable, simplePattern); if (entryPtr != NULL) { cmdPtr = Tcl_GetHashValue(entryPtr); if (!TclIsProc(cmdPtr)) { realCmdPtr = (Command *) TclGetOriginalCommand((Tcl_Command) cmdPtr); if (realCmdPtr != NULL && TclIsProc(realCmdPtr)) { goto simpleProcOK; } } else { simpleProcOK: if (specificNsInPattern) { elemObjPtr = Tcl_NewObj(); Tcl_GetCommandFullName(interp, (Tcl_Command) cmdPtr, elemObjPtr); } else { elemObjPtr = Tcl_NewStringObj(simplePattern, -1); } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } } } else #endif /* !INFO_PROCS_SEARCH_GLOBAL_NS */ { entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); while (entryPtr != NULL) { cmdName = Tcl_GetHashKey(&nsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { cmdPtr = Tcl_GetHashValue(entryPtr); if (!TclIsProc(cmdPtr)) { realCmdPtr = (Command *) TclGetOriginalCommand((Tcl_Command) cmdPtr); if (realCmdPtr != NULL && TclIsProc(realCmdPtr)) { goto procOK; } } else { procOK: if (specificNsInPattern) { elemObjPtr = Tcl_NewObj(); Tcl_GetCommandFullName(interp, (Tcl_Command) cmdPtr, 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 :: procs that match the simple pattern. Of course, we add in * only those procs that aren't hidden by a proc in the effective * namespace. */ #ifdef INFO_PROCS_SEARCH_GLOBAL_NS /* * If "info procs" worked like "info commands", returning the commands * also seen in the global namespace, then you would include this * code. As this could break backwards compatibilty with 8.0-8.2, we * decided not to "fix" it in 8.3, leaving the behavior slightly * different. */ if ((nsPtr != globalNsPtr) && !specificNsInPattern) { entryPtr = Tcl_FirstHashEntry(&globalNsPtr->cmdTable, &search); while (entryPtr != NULL) { cmdName = Tcl_GetHashKey(&globalNsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { if (Tcl_FindHashEntry(&nsPtr->cmdTable,cmdName) == NULL) { cmdPtr = Tcl_GetHashValue(entryPtr); realCmdPtr = (Command *) TclGetOriginalCommand( (Tcl_Command) cmdPtr); if (TclIsProc(cmdPtr) || ((realCmdPtr != NULL) && TclIsProc(realCmdPtr))) { Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(cmdName, -1)); } } } entryPtr = Tcl_NextHashEntry(&search); } } #endif } 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( ClientData dummy, /* Not used. */ 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( ClientData dummy, /* Not used. */ 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( ClientData dummy, /* Not used. */ 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; } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { int listLen, i; Tcl_Obj *resObjPtr, *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 (TclListObjGetElements(interp, objv[1], &listLen, &elemPtrs) != TCL_OK) { return TCL_ERROR; } joinObjPtr = (objc == 2) ? Tcl_NewStringObj(" ", 1) : objv[2]; Tcl_IncrRefCount(joinObjPtr); resObjPtr = Tcl_NewObj(); for (i = 0; i < listLen; i++) { if (i > 0) { Tcl_AppendObjToObj(resObjPtr, joinObjPtr); } Tcl_AppendObjToObj(resObjPtr, elemPtrs[i]); } Tcl_DecrRefCount(joinObjPtr); Tcl_SetObjResult(interp, resObjPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *listCopyPtr; Tcl_Obj **listObjv; /* The contents of the list. */ int listObjc; /* The length of the list. */ int code = TCL_OK; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "list ?varName ...?"); return TCL_ERROR; } listCopyPtr = TclListObjCopy(interp, objv[1]); if (listCopyPtr == NULL) { return TCL_ERROR; } TclListObjGetElements(NULL, listCopyPtr, &listObjc, &listObjv); objc -= 2; objv += 2; while (code == TCL_OK && objc > 0 && listObjc > 0) { if (NULL == Tcl_ObjSetVar2(interp, *objv++, NULL, *listObjv++, TCL_LEAVE_ERR_MSG)) { code = TCL_ERROR; } objc--; listObjc--; } if (code == TCL_OK && objc > 0) { Tcl_Obj *emptyObj; TclNewObj(emptyObj); Tcl_IncrRefCount(emptyObj); while (code == TCL_OK && objc-- > 0) { if (NULL == Tcl_ObjSetVar2(interp, *objv++, NULL, emptyObj, TCL_LEAVE_ERR_MSG)) { code = TCL_ERROR; } } Tcl_DecrRefCount(emptyObj); } if (code == TCL_OK && listObjc > 0) { Tcl_SetObjResult(interp, Tcl_NewListObj(listObjc, listObjv)); } Tcl_DecrRefCount(listCopyPtr); return code; } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ 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; } else { 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ register int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *listPtr; int index, len, 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); } if ((objc == 4) && (index == len)) { /* * Special case: insert one element at the end of the list. */ Tcl_ListObjAppendElement(NULL, listPtr, objv[3]); } else { Tcl_ListObjReplace(NULL, listPtr, index, 0, (objc-3), &(objv[3])); } /* * 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ register int objc, /* Number of arguments. */ register 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ register Tcl_Obj *const objv[]) /* Argument objects. */ { int listLen, result; 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. */ Tcl_SetObjResult(interp, Tcl_NewIntObj(listLen)); 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( ClientData notUsed, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ register Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj **elemPtrs; int listLen, first, last, result; 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; } if (first < 0) { first = 0; } result = TclGetIntForIndexM(interp, objv[3], /*endValue*/ listLen - 1, &last); if (result != TCL_OK) { return result; } if (last >= listLen) { last = (listLen - 1); } if (first > last) { /* * Returning an empty list is easy. */ return TCL_OK; } result = TclListObjGetElements(interp, objv[1], &listLen, &elemPtrs); if (result != TCL_OK) { return result; } if (Tcl_IsShared(objv[1]) || (((List *) objv[1]->internalRep.twoPtrValue.ptr1)->refCount > 1)) { Tcl_SetObjResult(interp, Tcl_NewListObj(last - first + 1, &(elemPtrs[first]))); } else { /* * In-place is possible. */ if (last < (listLen - 1)) { Tcl_ListObjReplace(interp, objv[1], last + 1, listLen - 1 - last, 0, NULL); } /* * This one is not conditioned on (first>0) in order to * preserve the string-canonizing effect of [lrange 0 end]. */ Tcl_ListObjReplace(interp, objv[1], 0, first, 0, NULL); Tcl_SetObjResult(interp, objv[1]); } return TCL_OK; } /* *---------------------------------------------------------------------- * * 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( ClientData dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ register int objc, /* Number of arguments. */ register Tcl_Obj *const objv[]) /* The argument objects. */ { int elementCount, i, totalElems; Tcl_Obj *listPtr, **dataArray; List *listRepPtr; /* * Check arguments for legality: * lrepeat count ?value ...? */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "count ?value ...?"); return TCL_ERROR; } if (TCL_OK != TclGetIntFromObj(interp, objv[1], &elementCount)) { return TCL_ERROR; } if (elementCount < 0) { Tcl_SetObjResult(interp, Tcl_Format(NULL, "bad count \"%d\": must be integer >= 0", 1, objv+1)); return TCL_ERROR; } /* * Skip forward to the interesting arguments now we've finished parsing. */ objc -= 2; objv += 2; /* * Final sanity check. Total number of elements must fit in a signed * integer. We also limit the number of elements to 512M-1 so allocations * on 32-bit machines are guaranteed to be less than 2GB! [Bug 2130992] */ totalElems = objc * elementCount; if (totalElems != 0 && (totalElems/objc != elementCount || totalElems/elementCount != objc)) { Tcl_AppendResult(interp, "too many elements in result list", NULL); return TCL_ERROR; } if (totalElems >= 0x20000000) { Tcl_AppendResult(interp, "too many elements in result list", NULL); return TCL_ERROR; } /* * Get an empty list object that is allocated large enough to hold each * init value elementCount times. */ listPtr = Tcl_NewListObj(totalElems, NULL); listRepPtr = listPtr->internalRep.twoPtrValue.ptr1; listRepPtr->elemCount = elementCount*objc; dataArray = &listRepPtr->elements; /* * 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. */ if (objc == 1) { register Tcl_Obj *tmpPtr = objv[0]; tmpPtr->refCount += elementCount; for (i=0 ; i= listLen) && (listLen > 0)) { Tcl_AppendResult(interp, "list doesn't contain element ", TclGetString(objv[2]), NULL); return TCL_ERROR; } if (last >= listLen) { last = (listLen - 1); } if (first <= last) { numToDelete = (last - first + 1); } 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. */ Tcl_ListObjReplace(NULL, listPtr, first, numToDelete, objc-4, &(objv[4])); /* * 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( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument values. */ { Tcl_Obj **elemv; int elemc, i, j; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "list"); return TCL_ERROR; } if (TclListObjGetElements(interp, objv[1], &elemc, &elemv) != 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 (Tcl_IsShared(objv[1])) { Tcl_Obj *resultObj, **dataArray; List *listPtr; makeNewReversedList: resultObj = Tcl_NewListObj(elemc, NULL); listPtr = (List *) resultObj->internalRep.twoPtrValue.ptr1; listPtr->elemCount = elemc; dataArray = &listPtr->elements; for (i=0,j=elemc-1 ; iinternalRep.twoPtrValue.ptr1)->refCount > 1) { goto makeNewReversedList; } /* * Not shared, so swap "in place". This relies on Tcl_LOGE above * returning a pointer to the live array of Tcl_Obj values. */ for (i=0,j=elemc-1 ; i 1) { ckfree((char *) sortInfo.indexv); } return TCL_ERROR; } switch ((enum options) index) { case LSEARCH_ALL: /* -all */ allMatches = 1; break; case LSEARCH_ASCII: /* -ascii */ dataType = ASCII; break; case LSEARCH_BISECT: /* -bisect */ mode = SORTED; bisect = 1; break; case LSEARCH_DECREASING: /* -decreasing */ isIncreasing = 0; sortInfo.isIncreasing = 0; break; case LSEARCH_DICTIONARY: /* -dictionary */ dataType = DICTIONARY; break; case LSEARCH_EXACT: /* -increasing */ mode = EXACT; break; case LSEARCH_GLOB: /* -glob */ mode = GLOB; break; case LSEARCH_INCREASING: /* -increasing */ isIncreasing = 1; sortInfo.isIncreasing = 1; break; case LSEARCH_INLINE: /* -inline */ inlineReturn = 1; break; case LSEARCH_INTEGER: /* -integer */ dataType = INTEGER; break; case LSEARCH_NOCASE: /* -nocase */ strCmpFn = strcasecmp; noCase = 1; break; case LSEARCH_NOT: /* -not */ negatedMatch = 1; break; case LSEARCH_REAL: /* -real */ dataType = REAL; break; case LSEARCH_REGEXP: /* -regexp */ mode = REGEXP; break; case LSEARCH_SORTED: /* -sorted */ mode = SORTED; break; case LSEARCH_SUBINDICES: /* -subindices */ returnSubindices = 1; break; case LSEARCH_START: /* -start */ /* * If there was a previous -start option, release its saved index * because it will either be replaced or there will be an error. */ if (startPtr != NULL) { Tcl_DecrRefCount(startPtr); } if (i > objc-4) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } Tcl_AppendResult(interp, "missing starting index", NULL); return TCL_ERROR; } 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_INDEX: { /* -index */ Tcl_Obj **indices; int j; if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } if (i > objc-4) { if (startPtr != NULL) { Tcl_DecrRefCount(startPtr); } Tcl_AppendResult(interp, "\"-index\" option must be followed by list index", NULL); return TCL_ERROR; } /* * 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) { if (startPtr != NULL) { Tcl_DecrRefCount(startPtr); } return TCL_ERROR; } switch (sortInfo.indexc) { case 0: sortInfo.indexv = NULL; break; case 1: sortInfo.indexv = &sortInfo.singleIndex; break; default: sortInfo.indexv = (int *) ckalloc(sizeof(int) * sortInfo.indexc); } /* * 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) { ckfree((char *) sortInfo.indexv); } Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (-index option item number %d)", j)); return TCL_ERROR; } } break; } } } /* * Subindices only make sense if asked for with -index option set. */ if (returnSubindices && sortInfo.indexc==0) { if (startPtr != NULL) { Tcl_DecrRefCount(startPtr); } Tcl_AppendResult(interp, "-subindices cannot be used without -index option", NULL); return TCL_ERROR; } if (bisect && (allMatches || negatedMatch)) { Tcl_AppendResult(interp, "-bisect is not compatible with -all or -not", NULL); return TCL_ERROR; } if (mode == REGEXP) { /* * We can shimmer regexp/list if listv[i] == pattern, so get the * regexp rep before the list rep. First time round, omit the interp * and hope that the compilation will succeed. If it fails, we'll * recompile in "expensive" mode with a place to put error messages. */ regexp = Tcl_GetRegExpFromObj(NULL, objv[objc - 1], TCL_REG_ADVANCED | TCL_REG_NOSUB | (noCase ? TCL_REG_NOCASE : 0)); if (regexp == NULL) { /* * Failed to compile the RE. Try again without the TCL_REG_NOSUB * flag in case the RE had sub-expressions in it [Bug 1366683]. If * this fails, an error message will be left in the interpreter. */ regexp = Tcl_GetRegExpFromObj(interp, objv[objc - 1], TCL_REG_ADVANCED | (noCase ? TCL_REG_NOCASE : 0)); } if (regexp == NULL) { if (startPtr != NULL) { Tcl_DecrRefCount(startPtr); } if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } 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. */ result = TclListObjGetElements(interp, objv[objc - 2], &listc, &listv); if (result != TCL_OK) { if (startPtr != NULL) { Tcl_DecrRefCount(startPtr); } if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return result; } /* * Get the user-specified start offset. */ if (startPtr) { result = TclGetIntForIndexM(interp, startPtr, listc-1, &offset); Tcl_DecrRefCount(startPtr); if (result != TCL_OK) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return result; } if (offset < 0) { offset = 0; } /* * 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 (offset > listc-1) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } if (allMatches || inlineReturn) { Tcl_ResetResult(interp); } else { Tcl_SetObjResult(interp, Tcl_NewIntObj(-1)); } return TCL_OK; } } patObj = objv[objc - 1]; patternBytes = NULL; if (mode == EXACT || mode == SORTED) { switch ((enum datatypes) dataType) { case ASCII: case DICTIONARY: patternBytes = TclGetStringFromObj(patObj, &length); break; case INTEGER: result = TclGetIntFromObj(interp, patObj, &patInt); if (result != TCL_OK) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return result; } /* * 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) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return result; } /* * 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. */ lower = offset - 1; upper = listc; while (lower + 1 != upper && sortInfo.resultCode == TCL_OK) { i = (lower + upper)/2; if (sortInfo.indexc != 0) { itemPtr = SelectObjFromSublist(listv[i], &sortInfo); if (sortInfo.resultCode != TCL_OK) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return sortInfo.resultCode; } } else { itemPtr = listv[i]; } switch ((enum datatypes) dataType) { case ASCII: bytes = TclGetString(itemPtr); match = strCmpFn(patternBytes, bytes); break; case DICTIONARY: bytes = TclGetString(itemPtr); match = DictionaryCompare(patternBytes, bytes); break; case INTEGER: result = TclGetIntFromObj(interp, itemPtr, &objInt); if (result != TCL_OK) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return result; } if (patInt == objInt) { match = 0; } else if (patInt < objInt) { match = -1; } else { match = 1; } break; case REAL: result = Tcl_GetDoubleFromObj(interp, itemPtr, &objDouble); if (result != TCL_OK) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return result; } 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 occurance. * Consider: 0 0 0 1 1 1 2 2 2 * * To maintain consistancy with standard lsearch semantics, we * must find the leftmost occurance 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 = offset; i < listc; i++) { match = 0; if (sortInfo.indexc != 0) { itemPtr = SelectObjFromSublist(listv[i], &sortInfo); if (sortInfo.resultCode != TCL_OK) { if (listPtr != NULL) { Tcl_DecrRefCount(listPtr); } if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return sortInfo.resultCode; } } else { itemPtr = listv[i]; } switch (mode) { case SORTED: case EXACT: switch ((enum datatypes) dataType) { case ASCII: bytes = TclGetStringFromObj(itemPtr, &elemLen); if (length == elemLen) { /* * This split allows for more optimal compilation of * memcmp/strcasecmp. */ if (noCase) { match = (strcasecmp(bytes, patternBytes) == 0); } else { match = (memcmp(bytes, patternBytes, (size_t) length) == 0); } } break; case DICTIONARY: bytes = TclGetString(itemPtr); match = (DictionaryCompare(bytes, patternBytes) == 0); break; case INTEGER: result = TclGetIntFromObj(interp, itemPtr, &objInt); if (result != TCL_OK) { if (listPtr != NULL) { Tcl_DecrRefCount(listPtr); } if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return result; } match = (objInt == patInt); break; case REAL: result = Tcl_GetDoubleFromObj(interp,itemPtr, &objDouble); if (result != TCL_OK) { if (listPtr) { Tcl_DecrRefCount(listPtr); } if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return result; } 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); } if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return TCL_ERROR; } 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)) { itemPtr = SelectObjFromSublist(listv[i], &sortInfo); } else { itemPtr = listv[i]; } Tcl_ListObjAppendElement(interp, listPtr, itemPtr); } else if (returnSubindices) { int j; itemPtr = Tcl_NewIntObj(i); for (j=0 ; j 1) { ckfree((char *) sortInfo.indexv); } return TCL_OK; } /* *---------------------------------------------------------------------- * * 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( ClientData clientData, /* Not used. */ 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], (Tcl_Obj *) 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 { 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( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument values. */ { int i, j, index, unique, indices, length, nocase = 0, sortMode, indexc; int group, groupSize, groupOffset, idx; Tcl_Obj *resultPtr, *cmdPtr, **listObjPtrs, *listObj, *indexPtr; SortElement *elementArray, *elementPtr; SortInfo sortInfo; /* Information about this sort that needs to * be passed to the comparison function. */ static const char *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 }; /* * The subList array below holds pointers to temporary lists built during * the merge sort. Element i of the array holds a list of length 2**i. */ # define NUM_LISTS 30 SortElement *subList[NUM_LISTS+1]; 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; unique = 0; indices = 0; group = 0; groupSize = 1; groupOffset = 0; for (i = 1; i < objc-1; i++) { if (Tcl_GetIndexFromObj(interp, objv[i], switches, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch ((enum Lsort_Switches) index) { case LSORT_ASCII: sortInfo.sortMode = SORTMODE_ASCII; break; case LSORT_COMMAND: if (i == (objc-2)) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } Tcl_AppendResult(interp, "\"-command\" option must be followed " "by comparison command", NULL); return TCL_ERROR; } 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_Obj **indices; if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } if (i == (objc-2)) { Tcl_AppendResult(interp, "\"-index\" option must be " "followed by list index", NULL); return TCL_ERROR; } /* * Take copy to prevent shimmering problems. */ if (TclListObjGetElements(interp, objv[i+1], &sortInfo.indexc, &indices) != TCL_OK) { return TCL_ERROR; } switch (sortInfo.indexc) { case 0: sortInfo.indexv = NULL; break; case 1: sortInfo.indexv = &sortInfo.singleIndex; break; default: sortInfo.indexv = (int *) ckalloc(sizeof(int) * sortInfo.indexc); } /* * 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) { ckfree((char *) sortInfo.indexv); } Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (-index option item number %d)", j)); return TCL_ERROR; } } i++; break; } case LSORT_INTEGER: sortInfo.sortMode = SORTMODE_INTEGER; break; case LSORT_NOCASE: nocase = 1; break; case LSORT_REAL: sortInfo.sortMode = SORTMODE_REAL; break; case LSORT_UNIQUE: unique = 1; sortInfo.unique = 1; break; case LSORT_INDICES: indices = 1; break; case LSORT_STRIDE: if (i == (objc-2)) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } Tcl_AppendResult(interp, "\"-stride\" option must be followed by stride length", NULL); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[i+1], &groupSize) != TCL_OK) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return TCL_ERROR; } if (groupSize < 2) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } Tcl_AppendResult(interp, "stride length must be at least 2", NULL); return TCL_ERROR; } group = 1; i++; break; } } if (nocase && (sortInfo.sortMode == SORTMODE_ASCII)) { sortInfo.sortMode = SORTMODE_ASCII_NC; } listObj = objv[objc-1]; if (sortInfo.sortMode == SORTMODE_COMMAND) { Tcl_Obj *newCommandPtr, *newObjPtr; /* * When sorting using a command, we are reentrant and therefore might * have the representation of the list being sorted shimmered out from * underneath our feet. Take a copy (cheap) to prevent this. [Bug * 1675116] */ listObj = TclListObjCopy(interp, listObj); if (listObj == NULL) { if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return TCL_ERROR; } /* * The existing command is a list. We want to flatten it, append two * dummy arguments on the end, and replace these arguments later. */ newCommandPtr = Tcl_DuplicateObj(cmdPtr); TclNewObj(newObjPtr); Tcl_IncrRefCount(newCommandPtr); if (Tcl_ListObjAppendElement(interp, newCommandPtr, newObjPtr) != TCL_OK) { TclDecrRefCount(newCommandPtr); TclDecrRefCount(listObj); Tcl_IncrRefCount(newObjPtr); TclDecrRefCount(newObjPtr); if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return TCL_ERROR; } Tcl_ListObjAppendElement(interp, newCommandPtr, Tcl_NewObj()); sortInfo.compareCmdPtr = newCommandPtr; } sortInfo.resultCode = TclListObjGetElements(interp, listObj, &length, &listObjPtrs); if (sortInfo.resultCode != TCL_OK || length <= 0) { goto done; } /* * Check for sanity when grouping elements of the overall list together * because of the -stride option. [TIP #326] */ if (group) { if (length % groupSize) { Tcl_AppendResult(interp, "list size must be a multiple of the stride length", NULL); sortInfo.resultCode = TCL_ERROR; goto done; } length = length / groupSize; 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 = sortInfo.indexv[0]; if (groupOffset <= SORTIDX_END) { groupOffset = (groupOffset - SORTIDX_END) + groupSize - 1; } if (groupOffset < 0 || groupOffset >= groupSize) { Tcl_AppendResult(interp, "when used with \"-stride\", the " "leading \"-index\" value must be within the group", NULL); sortInfo.resultCode = TCL_ERROR; goto done; } if (sortInfo.indexc == 1) { sortInfo.indexc = 0; sortInfo.indexv = NULL; } else { int *new_indexv; sortInfo.indexc--; new_indexv = (int *) ckalloc(sizeof(int) * sortInfo.indexc); for (i = 0; i < sortInfo.indexc; i++) { new_indexv[i] = sortInfo.indexv[i+1]; } ckfree((char *) sortInfo.indexv); sortInfo.indexv = new_indexv; } } } 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. */ elementArray = (SortElement *) ckalloc( length * sizeof(SortElement)); 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 done1; } } else { indexPtr = listObjPtrs[idx]; } /* * Determine the "value" of this object for sorting purposes */ if (sortMode == SORTMODE_ASCII) { elementArray[i].index.strValuePtr = TclGetString(indexPtr); } else if (sortMode == SORTMODE_INTEGER) { long a; if (TclGetLongFromObj(sortInfo.interp, indexPtr, &a) != TCL_OK) { sortInfo.resultCode = TCL_ERROR; goto done1; } elementArray[i].index.intValue = a; } else if (sortInfo.sortMode == SORTMODE_REAL) { double a; if (Tcl_GetDoubleFromObj(sortInfo.interp, indexPtr, &a) != TCL_OK) { sortInfo.resultCode = TCL_ERROR; goto done1; } elementArray[i].index.doubleValue = a; } else { elementArray[i].index.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].objPtr = INT2PTR(idx); } else { elementArray[i].objPtr = listObjPtrs[idx]; } /* * Merge this element in the pre-existing 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 ; jinternalRep.twoPtrValue.ptr1; newArray = &listRepPtr->elements; if (group) { for (i=0; elementPtr!=NULL ; elementPtr=elementPtr->nextPtr) { idx = PTR2INT(elementPtr->objPtr); for (j = 0; j < groupSize; j++) { if (indices) { objPtr = Tcl_NewIntObj(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) { objPtr = Tcl_NewIntObj(PTR2INT(elementPtr->objPtr)); newArray[i++] = objPtr; Tcl_IncrRefCount(objPtr); } } else { for (i=0; elementPtr != NULL ; elementPtr = elementPtr->nextPtr) { objPtr = elementPtr->objPtr; newArray[i++] = objPtr; Tcl_IncrRefCount(objPtr); } } listRepPtr->elemCount = i; Tcl_SetObjResult(interp, resultPtr); } done1: ckfree((char *) elementArray); done: if (sortInfo.sortMode == SORTMODE_COMMAND) { TclDecrRefCount(sortInfo.compareCmdPtr); TclDecrRefCount(listObj); sortInfo.compareCmdPtr = NULL; } if (sortInfo.indexc > 1) { ckfree((char *) sortInfo.indexv); } return sortInfo.resultCode; } /* *---------------------------------------------------------------------- * * 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 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 = strcmp(elemPtr1->index.strValuePtr, elemPtr2->index.strValuePtr); } else if (infoPtr->sortMode == SORTMODE_ASCII_NC) { order = strcasecmp(elemPtr1->index.strValuePtr, elemPtr2->index.strValuePtr); } else if (infoPtr->sortMode == SORTMODE_DICTIONARY) { order = DictionaryCompare(elemPtr1->index.strValuePtr, elemPtr2->index.strValuePtr); } else if (infoPtr->sortMode == SORTMODE_INTEGER) { long a, b; a = elemPtr1->index.intValue; b = elemPtr2->index.intValue; order = ((a >= b) - (a <= b)); } else if (infoPtr->sortMode == SORTMODE_REAL) { double a, b; a = elemPtr1->index.doubleValue; b = elemPtr2->index.doubleValue; order = ((a >= b) - (a <= b)); } else { Tcl_Obj **objv, *paramObjv[2]; int 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->index.objValuePtr; objPtr2 = elemPtr2->index.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_ResetResult(infoPtr->interp); Tcl_AppendResult(infoPtr->interp, "-compare command returned non-integer result", 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( char *left, char *right) /* The strings to compare. */ { Tcl_UniChar uniLeft, uniRight, 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 += Tcl_UtfToUniChar(left, &uniLeft); right += Tcl_UtfToUniChar(right, &uniRight); /* * Convert both chars to lower for the comparison, because * dictionary sorts are case insensitve. 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. */ { int 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++) { int listLen, index; Tcl_Obj *currentObj; if (TclListObjLength(infoPtr->interp, objPtr, &listLen) != TCL_OK) { infoPtr->resultCode = TCL_ERROR; return NULL; } index = infoPtr->indexv[i]; /* * Adjust for end-based indexing. */ if (index < SORTIDX_NONE) { index += listLen + 1; } if (Tcl_ListObjIndex(infoPtr->interp, objPtr, index, ¤tObj) != TCL_OK) { infoPtr->resultCode = TCL_ERROR; return NULL; } if (currentObj == NULL) { char buffer[TCL_INTEGER_SPACE]; TclFormatInt(buffer, index); Tcl_AppendResult(infoPtr->interp, "element ", buffer, " missing from sublist \"", TclGetString(objPtr), "\"", NULL); infoPtr->resultCode = TCL_ERROR; return NULL; } objPtr = currentObj; } return objPtr; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */