sl@0: /* sl@0: * tclIndexObj.c -- sl@0: * sl@0: * This file implements objects of type "index". This object type sl@0: * is used to lookup a keyword in a table of valid values and cache sl@0: * the index of the matching entry. sl@0: * sl@0: * Copyright (c) 1997 Sun Microsystems, Inc. sl@0: * Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclIndexObj.c,v 1.16.2.5 2006/04/06 18:57:24 dgp Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: #include "tclPort.h" sl@0: sl@0: /* sl@0: * Prototypes for procedures defined later in this file: sl@0: */ sl@0: sl@0: static int SetIndexFromAny _ANSI_ARGS_((Tcl_Interp *interp, sl@0: Tcl_Obj *objPtr)); sl@0: static void UpdateStringOfIndex _ANSI_ARGS_((Tcl_Obj *objPtr)); sl@0: static void DupIndex _ANSI_ARGS_((Tcl_Obj *srcPtr, sl@0: Tcl_Obj *dupPtr)); sl@0: static void FreeIndex _ANSI_ARGS_((Tcl_Obj *objPtr)); sl@0: sl@0: /* sl@0: * The structure below defines the index Tcl object type by means of sl@0: * procedures that can be invoked by generic object code. sl@0: */ sl@0: sl@0: Tcl_ObjType tclIndexType = { sl@0: "index", /* name */ sl@0: FreeIndex, /* freeIntRepProc */ sl@0: DupIndex, /* dupIntRepProc */ sl@0: UpdateStringOfIndex, /* updateStringProc */ sl@0: SetIndexFromAny /* setFromAnyProc */ sl@0: }; sl@0: sl@0: /* sl@0: * The definition of the internal representation of the "index" sl@0: * object; The internalRep.otherValuePtr field of an object of "index" sl@0: * type will be a pointer to one of these structures. sl@0: * sl@0: * Keep this structure declaration in sync with tclTestObj.c sl@0: */ sl@0: sl@0: typedef struct { sl@0: VOID *tablePtr; /* Pointer to the table of strings */ sl@0: int offset; /* Offset between table entries */ sl@0: int index; /* Selected index into table. */ sl@0: } IndexRep; sl@0: sl@0: /* sl@0: * The following macros greatly simplify moving through a table... sl@0: */ sl@0: #define STRING_AT(table, offset, index) \ sl@0: (*((CONST char * CONST *)(((char *)(table)) + ((offset) * (index))))) sl@0: #define NEXT_ENTRY(table, offset) \ sl@0: (&(STRING_AT(table, offset, 1))) sl@0: #define EXPAND_OF(indexRep) \ sl@0: STRING_AT((indexRep)->tablePtr, (indexRep)->offset, (indexRep)->index) sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_GetIndexFromObj -- sl@0: * sl@0: * This procedure looks up an object's value in a table of strings sl@0: * and returns the index of the matching string, if any. sl@0: * sl@0: * Results: sl@0: * sl@0: * If the value of objPtr is identical to or a unique abbreviation sl@0: * for one of the entries in objPtr, then the return value is sl@0: * TCL_OK and the index of the matching entry is stored at sl@0: * *indexPtr. If there isn't a proper match, then TCL_ERROR is sl@0: * returned and an error message is left in interp's result (unless sl@0: * interp is NULL). The msg argument is used in the error sl@0: * message; for example, if msg has the value "option" then the sl@0: * error message will say something flag 'bad option "foo": must be sl@0: * ...' sl@0: * sl@0: * Side effects: sl@0: * The result of the lookup is cached as the internal rep of sl@0: * objPtr, so that repeated lookups can be done quickly. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_GetIndexFromObj(interp, objPtr, tablePtr, msg, flags, indexPtr) sl@0: Tcl_Interp *interp; /* Used for error reporting if not NULL. */ sl@0: Tcl_Obj *objPtr; /* Object containing the string to lookup. */ sl@0: CONST char **tablePtr; /* Array of strings to compare against the sl@0: * value of objPtr; last entry must be NULL sl@0: * and there must not be duplicate entries. */ sl@0: CONST char *msg; /* Identifying word to use in error messages. */ sl@0: int flags; /* 0 or TCL_EXACT */ sl@0: int *indexPtr; /* Place to store resulting integer index. */ sl@0: { sl@0: sl@0: /* sl@0: * See if there is a valid cached result from a previous lookup sl@0: * (doing the check here saves the overhead of calling sl@0: * Tcl_GetIndexFromObjStruct in the common case where the result sl@0: * is cached). sl@0: */ sl@0: sl@0: if (objPtr->typePtr == &tclIndexType) { sl@0: IndexRep *indexRep = (IndexRep *) objPtr->internalRep.otherValuePtr; sl@0: /* sl@0: * Here's hoping we don't get hit by unfortunate packing sl@0: * constraints on odd platforms like a Cray PVP... sl@0: */ sl@0: if (indexRep->tablePtr == (VOID *)tablePtr && sl@0: indexRep->offset == sizeof(char *)) { sl@0: *indexPtr = indexRep->index; sl@0: return TCL_OK; sl@0: } sl@0: } sl@0: return Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, sizeof(char *), sl@0: msg, flags, indexPtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_GetIndexFromObjStruct -- sl@0: * sl@0: * This procedure looks up an object's value given a starting sl@0: * string and an offset for the amount of space between strings. sl@0: * This is useful when the strings are embedded in some other sl@0: * kind of array. sl@0: * sl@0: * Results: sl@0: * sl@0: * If the value of objPtr is identical to or a unique abbreviation sl@0: * for one of the entries in objPtr, then the return value is sl@0: * TCL_OK and the index of the matching entry is stored at sl@0: * *indexPtr. If there isn't a proper match, then TCL_ERROR is sl@0: * returned and an error message is left in interp's result (unless sl@0: * interp is NULL). The msg argument is used in the error sl@0: * message; for example, if msg has the value "option" then the sl@0: * error message will say something flag 'bad option "foo": must be sl@0: * ...' sl@0: * sl@0: * Side effects: sl@0: * The result of the lookup is cached as the internal rep of sl@0: * objPtr, so that repeated lookups can be done quickly. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, offset, msg, flags, sl@0: indexPtr) sl@0: Tcl_Interp *interp; /* Used for error reporting if not NULL. */ sl@0: Tcl_Obj *objPtr; /* Object containing the string to lookup. */ sl@0: CONST VOID *tablePtr; /* The first string in the table. The second sl@0: * string will be at this address plus the sl@0: * offset, the third plus the offset again, sl@0: * etc. The last entry must be NULL sl@0: * and there must not be duplicate entries. */ sl@0: int offset; /* The number of bytes between entries */ sl@0: CONST char *msg; /* Identifying word to use in error messages. */ sl@0: int flags; /* 0 or TCL_EXACT */ sl@0: int *indexPtr; /* Place to store resulting integer index. */ sl@0: { sl@0: int index, i, numAbbrev; sl@0: char *key, *p1; sl@0: CONST char *p2; sl@0: CONST char * CONST *entryPtr; sl@0: Tcl_Obj *resultPtr; sl@0: IndexRep *indexRep; sl@0: sl@0: /* sl@0: * See if there is a valid cached result from a previous lookup. sl@0: */ sl@0: sl@0: if (objPtr->typePtr == &tclIndexType) { sl@0: indexRep = (IndexRep *) objPtr->internalRep.otherValuePtr; sl@0: if (indexRep->tablePtr==tablePtr && indexRep->offset==offset) { sl@0: *indexPtr = indexRep->index; sl@0: return TCL_OK; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Lookup the value of the object in the table. Accept unique sl@0: * abbreviations unless TCL_EXACT is set in flags. sl@0: */ sl@0: sl@0: key = TclGetString(objPtr); sl@0: index = -1; sl@0: numAbbrev = 0; sl@0: sl@0: /* sl@0: * Scan the table looking for one of: sl@0: * - An exact match (always preferred) sl@0: * - A single abbreviation (allowed depending on flags) sl@0: * - Several abbreviations (never allowed, but overridden by exact match) sl@0: */ sl@0: for (entryPtr = tablePtr, i = 0; *entryPtr != NULL; sl@0: entryPtr = NEXT_ENTRY(entryPtr, offset), i++) { sl@0: for (p1 = key, p2 = *entryPtr; *p1 == *p2; p1++, p2++) { sl@0: if (*p1 == '\0') { sl@0: index = i; sl@0: goto done; sl@0: } sl@0: } sl@0: if (*p1 == '\0') { sl@0: /* sl@0: * The value is an abbreviation for this entry. Continue sl@0: * checking other entries to make sure it's unique. If we sl@0: * get more than one unique abbreviation, keep searching to sl@0: * see if there is an exact match, but remember the number sl@0: * of unique abbreviations and don't allow either. sl@0: */ sl@0: sl@0: numAbbrev++; sl@0: index = i; sl@0: } sl@0: } sl@0: /* sl@0: * Check if we were instructed to disallow abbreviations. sl@0: */ sl@0: if ((flags & TCL_EXACT) || (key[0] == '\0') || (numAbbrev != 1)) { sl@0: goto error; sl@0: } sl@0: sl@0: done: sl@0: /* sl@0: * Cache the found representation. Note that we want to avoid sl@0: * allocating a new internal-rep if at all possible since that is sl@0: * potentially a slow operation. sl@0: */ sl@0: if (objPtr->typePtr == &tclIndexType) { sl@0: indexRep = (IndexRep *) objPtr->internalRep.otherValuePtr; sl@0: } else { sl@0: if ((objPtr->typePtr != NULL) sl@0: && (objPtr->typePtr->freeIntRepProc != NULL)) { sl@0: objPtr->typePtr->freeIntRepProc(objPtr); sl@0: } sl@0: indexRep = (IndexRep *) ckalloc(sizeof(IndexRep)); sl@0: objPtr->internalRep.otherValuePtr = (VOID *) indexRep; sl@0: objPtr->typePtr = &tclIndexType; sl@0: } sl@0: indexRep->tablePtr = (VOID*) tablePtr; sl@0: indexRep->offset = offset; sl@0: indexRep->index = index; sl@0: sl@0: *indexPtr = index; sl@0: return TCL_OK; sl@0: sl@0: error: sl@0: if (interp != NULL) { sl@0: /* sl@0: * Produce a fancy error message. sl@0: */ sl@0: int count; sl@0: sl@0: TclNewObj(resultPtr); sl@0: Tcl_SetObjResult(interp, resultPtr); sl@0: Tcl_AppendStringsToObj(resultPtr, (numAbbrev > 1) && sl@0: !(flags & TCL_EXACT) ? "ambiguous " : "bad ", msg, " \"", sl@0: key, "\": must be ", STRING_AT(tablePtr,offset,0), (char*)NULL); sl@0: for (entryPtr = NEXT_ENTRY(tablePtr, offset), count = 0; sl@0: *entryPtr != NULL; sl@0: entryPtr = NEXT_ENTRY(entryPtr, offset), count++) { sl@0: if (*NEXT_ENTRY(entryPtr, offset) == NULL) { sl@0: Tcl_AppendStringsToObj(resultPtr, sl@0: (count > 0) ? ", or " : " or ", *entryPtr, sl@0: (char *) NULL); sl@0: } else { sl@0: Tcl_AppendStringsToObj(resultPtr, ", ", *entryPtr, sl@0: (char *) NULL); sl@0: } sl@0: } sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SetIndexFromAny -- sl@0: * sl@0: * This procedure is called to convert a Tcl object to index sl@0: * internal form. However, this doesn't make sense (need to have a sl@0: * table of keywords in order to do the conversion) so the sl@0: * procedure always generates an error. sl@0: * sl@0: * Results: sl@0: * The return value is always TCL_ERROR, and an error message is sl@0: * left in interp's result if interp isn't NULL. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: SetIndexFromAny(interp, objPtr) sl@0: Tcl_Interp *interp; /* Used for error reporting if not NULL. */ sl@0: register Tcl_Obj *objPtr; /* The object to convert. */ sl@0: { sl@0: Tcl_AppendToObj(Tcl_GetObjResult(interp), sl@0: "can't convert value to index except via Tcl_GetIndexFromObj API", sl@0: -1); sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * UpdateStringOfIndex -- sl@0: * sl@0: * This procedure is called to convert a Tcl object from index sl@0: * internal form to its string form. No abbreviation is ever sl@0: * generated. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The string representation of the object is updated. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: UpdateStringOfIndex(objPtr) sl@0: Tcl_Obj *objPtr; sl@0: { sl@0: IndexRep *indexRep = (IndexRep *) objPtr->internalRep.otherValuePtr; sl@0: register char *buf; sl@0: register unsigned len; sl@0: register CONST char *indexStr = EXPAND_OF(indexRep); sl@0: sl@0: len = strlen(indexStr); sl@0: buf = (char *) ckalloc(len + 1); sl@0: memcpy(buf, indexStr, len+1); sl@0: objPtr->bytes = buf; sl@0: objPtr->length = len; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * DupIndex -- sl@0: * sl@0: * This procedure is called to copy the internal rep of an index sl@0: * Tcl object from to another object. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The internal representation of the target object is updated sl@0: * and the type is set. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: DupIndex(srcPtr, dupPtr) sl@0: Tcl_Obj *srcPtr, *dupPtr; sl@0: { sl@0: IndexRep *srcIndexRep = (IndexRep *) srcPtr->internalRep.otherValuePtr; sl@0: IndexRep *dupIndexRep = (IndexRep *) ckalloc(sizeof(IndexRep)); sl@0: sl@0: memcpy(dupIndexRep, srcIndexRep, sizeof(IndexRep)); sl@0: dupPtr->internalRep.otherValuePtr = (VOID *) dupIndexRep; sl@0: dupPtr->typePtr = &tclIndexType; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * FreeIndex -- sl@0: * sl@0: * This procedure is called to delete the internal rep of an index sl@0: * Tcl object. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The internal representation of the target object is deleted. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: FreeIndex(objPtr) sl@0: Tcl_Obj *objPtr; sl@0: { sl@0: ckfree((char *) objPtr->internalRep.otherValuePtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_WrongNumArgs -- sl@0: * sl@0: * This procedure generates a "wrong # args" error message in an sl@0: * interpreter. It is used as a utility function by many command sl@0: * procedures. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * An error message is generated in interp's result object to sl@0: * indicate that a command was invoked with the wrong number of sl@0: * arguments. The message has the form sl@0: * wrong # args: should be "foo bar additional stuff" sl@0: * where "foo" and "bar" are the initial objects in objv (objc sl@0: * determines how many of these are printed) and "additional stuff" sl@0: * is the contents of the message argument. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_WrongNumArgs(interp, objc, objv, message) sl@0: Tcl_Interp *interp; /* Current interpreter. */ sl@0: int objc; /* Number of arguments to print sl@0: * from objv. */ sl@0: Tcl_Obj *CONST objv[]; /* Initial argument objects, which sl@0: * should be included in the error sl@0: * message. */ sl@0: CONST char *message; /* Error message to print after the sl@0: * leading objects in objv. The sl@0: * message may be NULL. */ sl@0: { sl@0: Tcl_Obj *objPtr; sl@0: int i; sl@0: register IndexRep *indexRep; sl@0: sl@0: TclNewObj(objPtr); sl@0: Tcl_SetObjResult(interp, objPtr); sl@0: Tcl_AppendToObj(objPtr, "wrong # args: should be \"", -1); sl@0: for (i = 0; i < objc; i++) { sl@0: /* sl@0: * If the object is an index type use the index table which allows sl@0: * for the correct error message even if the subcommand was sl@0: * abbreviated. Otherwise, just use the string rep. sl@0: */ sl@0: sl@0: if (objv[i]->typePtr == &tclIndexType) { sl@0: indexRep = (IndexRep *) objv[i]->internalRep.otherValuePtr; sl@0: Tcl_AppendStringsToObj(objPtr, EXPAND_OF(indexRep), (char *) NULL); sl@0: } else { sl@0: Tcl_AppendStringsToObj(objPtr, Tcl_GetString(objv[i]), sl@0: (char *) NULL); sl@0: } sl@0: sl@0: /* sl@0: * Append a space character (" ") if there is more text to follow sl@0: * (either another element from objv, or the message string). sl@0: */ sl@0: if ((i < (objc - 1)) || message) { sl@0: Tcl_AppendStringsToObj(objPtr, " ", (char *) NULL); sl@0: } sl@0: } sl@0: sl@0: if (message) { sl@0: Tcl_AppendStringsToObj(objPtr, message, (char *) NULL); sl@0: } sl@0: Tcl_AppendStringsToObj(objPtr, "\"", (char *) NULL); sl@0: }