762 lines
23 KiB
C
762 lines
23 KiB
C
/*
|
||
* tclLink.c --
|
||
*
|
||
* This file implements linked variables (a C variable that is tied to a
|
||
* Tcl variable). The idea of linked variables was first suggested by
|
||
* Andreas Stolcke and this implementation is based heavily on a
|
||
* prototype implementation provided by him.
|
||
*
|
||
* Copyright (c) 1993 The Regents of the University of California.
|
||
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
|
||
*
|
||
* See the file "license.terms" for information on usage and redistribution of
|
||
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||
*/
|
||
|
||
#include "tclInt.h"
|
||
|
||
/*
|
||
* For each linked variable there is a data structure of the following type,
|
||
* which describes the link and is the clientData for the trace set on the Tcl
|
||
* variable.
|
||
*/
|
||
|
||
typedef struct Link {
|
||
Tcl_Interp *interp; /* Interpreter containing Tcl variable. */
|
||
Namespace *nsPtr; /* Namespace containing Tcl variable */
|
||
Tcl_Obj *varName; /* Name of variable (must be global). This is
|
||
* needed during trace callbacks, since the
|
||
* actual variable may be aliased at that time
|
||
* via upvar. */
|
||
char *addr; /* Location of C variable. */
|
||
int type; /* Type of link (TCL_LINK_INT, etc.). */
|
||
union {
|
||
char c;
|
||
unsigned char uc;
|
||
int i;
|
||
unsigned int ui;
|
||
short s;
|
||
unsigned short us;
|
||
long l;
|
||
unsigned long ul;
|
||
Tcl_WideInt w;
|
||
Tcl_WideUInt uw;
|
||
float f;
|
||
double d;
|
||
} lastValue; /* Last known value of C variable; used to
|
||
* avoid string conversions. */
|
||
int flags; /* Miscellaneous one-bit values; see below for
|
||
* definitions. */
|
||
} Link;
|
||
|
||
/*
|
||
* Definitions for flag bits:
|
||
* LINK_READ_ONLY - 1 means errors should be generated if Tcl
|
||
* script attempts to write variable.
|
||
* LINK_BEING_UPDATED - 1 means that a call to Tcl_UpdateLinkedVar is
|
||
* in progress for this variable, so trace
|
||
* callbacks on the variable should be ignored.
|
||
*/
|
||
|
||
#define LINK_READ_ONLY 1
|
||
#define LINK_BEING_UPDATED 2
|
||
|
||
/*
|
||
* Forward references to functions defined later in this file:
|
||
*/
|
||
|
||
static char * LinkTraceProc(ClientData clientData,Tcl_Interp *interp,
|
||
const char *name1, const char *name2, int flags);
|
||
static Tcl_Obj * ObjValue(Link *linkPtr);
|
||
static int GetInvalidIntFromObj(Tcl_Obj *objPtr, int *intPtr);
|
||
static int GetInvalidWideFromObj(Tcl_Obj *objPtr, Tcl_WideInt *widePtr);
|
||
static int GetInvalidDoubleFromObj(Tcl_Obj *objPtr, double *doublePtr);
|
||
|
||
/*
|
||
* Convenience macro for accessing the value of the C variable pointed to by a
|
||
* link. Note that this macro produces something that may be regarded as an
|
||
* lvalue or rvalue; it may be assigned to as well as read. Also note that
|
||
* this macro assumes the name of the variable being accessed (linkPtr); this
|
||
* is not strictly a good thing, but it keeps the code much shorter and
|
||
* cleaner.
|
||
*/
|
||
|
||
#define LinkedVar(type) (*(type *) linkPtr->addr)
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* Tcl_LinkVar --
|
||
*
|
||
* Link a C variable to a Tcl variable so that changes to either one
|
||
* causes the other to change.
|
||
*
|
||
* Results:
|
||
* The return value is TCL_OK if everything went well or TCL_ERROR if an
|
||
* error occurred (the interp's result is also set after errors).
|
||
*
|
||
* Side effects:
|
||
* The value at *addr is linked to the Tcl variable "varName", using
|
||
* "type" to convert between string values for Tcl and binary values for
|
||
* *addr.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
Tcl_LinkVar(
|
||
Tcl_Interp *interp, /* Interpreter in which varName exists. */
|
||
const char *varName, /* Name of a global variable in interp. */
|
||
char *addr, /* Address of a C variable to be linked to
|
||
* varName. */
|
||
int type) /* Type of C variable: TCL_LINK_INT, etc. Also
|
||
* may have TCL_LINK_READ_ONLY OR'ed in. */
|
||
{
|
||
Tcl_Obj *objPtr;
|
||
Link *linkPtr;
|
||
Namespace *dummy;
|
||
const char *name;
|
||
int code;
|
||
|
||
linkPtr = (Link *) Tcl_VarTraceInfo2(interp, varName, NULL,
|
||
TCL_GLOBAL_ONLY, LinkTraceProc, (ClientData) NULL);
|
||
if (linkPtr != NULL) {
|
||
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"variable '%s' is already linked", varName));
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
linkPtr = ckalloc(sizeof(Link));
|
||
linkPtr->interp = interp;
|
||
linkPtr->nsPtr = NULL;
|
||
linkPtr->varName = Tcl_NewStringObj(varName, -1);
|
||
Tcl_IncrRefCount(linkPtr->varName);
|
||
linkPtr->addr = addr;
|
||
linkPtr->type = type & ~TCL_LINK_READ_ONLY;
|
||
if (type & TCL_LINK_READ_ONLY) {
|
||
linkPtr->flags = LINK_READ_ONLY;
|
||
} else {
|
||
linkPtr->flags = 0;
|
||
}
|
||
objPtr = ObjValue(linkPtr);
|
||
if (Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, objPtr,
|
||
TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
|
||
Tcl_DecrRefCount(linkPtr->varName);
|
||
ckfree(linkPtr);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
TclGetNamespaceForQualName(interp, varName, NULL, TCL_GLOBAL_ONLY,
|
||
&(linkPtr->nsPtr), &dummy, &dummy, &name);
|
||
linkPtr->nsPtr->refCount++;
|
||
|
||
code = Tcl_TraceVar2(interp, varName, NULL,
|
||
TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
|
||
LinkTraceProc, linkPtr);
|
||
if (code != TCL_OK) {
|
||
Tcl_DecrRefCount(linkPtr->varName);
|
||
TclNsDecrRefCount(linkPtr->nsPtr);
|
||
ckfree(linkPtr);
|
||
}
|
||
return code;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* Tcl_UnlinkVar --
|
||
*
|
||
* Destroy the link between a Tcl variable and a C variable.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* If "varName" was previously linked to a C variable, the link is broken
|
||
* to make the variable independent. If there was no previous link for
|
||
* "varName" then nothing happens.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
Tcl_UnlinkVar(
|
||
Tcl_Interp *interp, /* Interpreter containing variable to unlink */
|
||
const char *varName) /* Global variable in interp to unlink. */
|
||
{
|
||
Link *linkPtr = (Link *) Tcl_VarTraceInfo2(interp, varName, NULL,
|
||
TCL_GLOBAL_ONLY, LinkTraceProc, NULL);
|
||
|
||
if (linkPtr == NULL) {
|
||
return;
|
||
}
|
||
Tcl_UntraceVar2(interp, varName, NULL,
|
||
TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
|
||
LinkTraceProc, linkPtr);
|
||
Tcl_DecrRefCount(linkPtr->varName);
|
||
if (linkPtr->nsPtr) {
|
||
TclNsDecrRefCount(linkPtr->nsPtr);
|
||
}
|
||
ckfree(linkPtr);
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* Tcl_UpdateLinkedVar --
|
||
*
|
||
* This function is invoked after a linked variable has been changed by C
|
||
* code. It updates the Tcl variable so that traces on the variable will
|
||
* trigger.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* The Tcl variable "varName" is updated from its C value, causing traces
|
||
* on the variable to trigger.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
Tcl_UpdateLinkedVar(
|
||
Tcl_Interp *interp, /* Interpreter containing variable. */
|
||
const char *varName) /* Name of global variable that is linked. */
|
||
{
|
||
Link *linkPtr = (Link *) Tcl_VarTraceInfo2(interp, varName, NULL,
|
||
TCL_GLOBAL_ONLY, LinkTraceProc, NULL);
|
||
int savedFlag;
|
||
|
||
if (linkPtr == NULL) {
|
||
return;
|
||
}
|
||
savedFlag = linkPtr->flags & LINK_BEING_UPDATED;
|
||
linkPtr->flags |= LINK_BEING_UPDATED;
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
/*
|
||
* Callback may have unlinked the variable. [Bug 1740631]
|
||
*/
|
||
linkPtr = (Link *) Tcl_VarTraceInfo2(interp, varName, NULL,
|
||
TCL_GLOBAL_ONLY, LinkTraceProc, NULL);
|
||
if (linkPtr != NULL) {
|
||
linkPtr->flags = (linkPtr->flags & ~LINK_BEING_UPDATED) | savedFlag;
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* LinkTraceProc --
|
||
*
|
||
* This function is invoked when a linked Tcl variable is read, written,
|
||
* or unset from Tcl. It's responsible for keeping the C variable in sync
|
||
* with the Tcl variable.
|
||
*
|
||
* Results:
|
||
* If all goes well, NULL is returned; otherwise an error message is
|
||
* returned.
|
||
*
|
||
* Side effects:
|
||
* The C variable may be updated to make it consistent with the Tcl
|
||
* variable, or the Tcl variable may be overwritten to reject a
|
||
* modification.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static char *
|
||
LinkTraceProc(
|
||
ClientData clientData, /* Contains information about the link. */
|
||
Tcl_Interp *interp, /* Interpreter containing Tcl variable. */
|
||
const char *name1, /* First part of variable name. */
|
||
const char *name2, /* Second part of variable name. */
|
||
int flags) /* Miscellaneous additional information. */
|
||
{
|
||
Link *linkPtr = clientData;
|
||
int changed;
|
||
size_t valueLength;
|
||
const char *value;
|
||
char **pp;
|
||
Tcl_Obj *valueObj;
|
||
int valueInt;
|
||
Tcl_WideInt valueWide;
|
||
double valueDouble;
|
||
|
||
/*
|
||
* If the variable is being unset, then just re-create it (with a trace)
|
||
* unless the whole interpreter is going away.
|
||
*/
|
||
|
||
if (flags & TCL_TRACE_UNSETS) {
|
||
if (Tcl_InterpDeleted(interp) || TclNamespaceDeleted(linkPtr->nsPtr)) {
|
||
Tcl_DecrRefCount(linkPtr->varName);
|
||
if (linkPtr->nsPtr) {
|
||
TclNsDecrRefCount(linkPtr->nsPtr);
|
||
}
|
||
ckfree(linkPtr);
|
||
} else if (flags & TCL_TRACE_DESTROYED) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
Tcl_TraceVar2(interp, Tcl_GetString(linkPtr->varName), NULL,
|
||
TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES
|
||
|TCL_TRACE_UNSETS, LinkTraceProc, linkPtr);
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* If we were invoked because of a call to Tcl_UpdateLinkedVar, then don't
|
||
* do anything at all. In particular, we don't want to get upset that the
|
||
* variable is being modified, even if it is supposed to be read-only.
|
||
*/
|
||
|
||
if (linkPtr->flags & LINK_BEING_UPDATED) {
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* For read accesses, update the Tcl variable if the C variable has
|
||
* changed since the last time we updated the Tcl variable.
|
||
*/
|
||
|
||
if (flags & TCL_TRACE_READS) {
|
||
switch (linkPtr->type) {
|
||
case TCL_LINK_INT:
|
||
case TCL_LINK_BOOLEAN:
|
||
changed = (LinkedVar(int) != linkPtr->lastValue.i);
|
||
break;
|
||
case TCL_LINK_DOUBLE:
|
||
changed = (LinkedVar(double) != linkPtr->lastValue.d);
|
||
break;
|
||
case TCL_LINK_WIDE_INT:
|
||
changed = (LinkedVar(Tcl_WideInt) != linkPtr->lastValue.w);
|
||
break;
|
||
case TCL_LINK_WIDE_UINT:
|
||
changed = (LinkedVar(Tcl_WideUInt) != linkPtr->lastValue.uw);
|
||
break;
|
||
case TCL_LINK_CHAR:
|
||
changed = (LinkedVar(char) != linkPtr->lastValue.c);
|
||
break;
|
||
case TCL_LINK_UCHAR:
|
||
changed = (LinkedVar(unsigned char) != linkPtr->lastValue.uc);
|
||
break;
|
||
case TCL_LINK_SHORT:
|
||
changed = (LinkedVar(short) != linkPtr->lastValue.s);
|
||
break;
|
||
case TCL_LINK_USHORT:
|
||
changed = (LinkedVar(unsigned short) != linkPtr->lastValue.us);
|
||
break;
|
||
case TCL_LINK_UINT:
|
||
changed = (LinkedVar(unsigned int) != linkPtr->lastValue.ui);
|
||
break;
|
||
case TCL_LINK_LONG:
|
||
changed = (LinkedVar(long) != linkPtr->lastValue.l);
|
||
break;
|
||
case TCL_LINK_ULONG:
|
||
changed = (LinkedVar(unsigned long) != linkPtr->lastValue.ul);
|
||
break;
|
||
case TCL_LINK_FLOAT:
|
||
changed = (LinkedVar(float) != linkPtr->lastValue.f);
|
||
break;
|
||
case TCL_LINK_STRING:
|
||
changed = 1;
|
||
break;
|
||
default:
|
||
return (char *) "internal error: bad linked variable type";
|
||
}
|
||
if (changed) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* For writes, first make sure that the variable is writable. Then convert
|
||
* the Tcl value to C if possible. If the variable isn't writable or can't
|
||
* be converted, then restore the varaible's old value and return an
|
||
* error. Another tricky thing: we have to save and restore the interp's
|
||
* result, since the variable access could occur when the result has been
|
||
* partially set.
|
||
*/
|
||
|
||
if (linkPtr->flags & LINK_READ_ONLY) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "linked variable is read-only";
|
||
}
|
||
valueObj = Tcl_ObjGetVar2(interp, linkPtr->varName,NULL, TCL_GLOBAL_ONLY);
|
||
if (valueObj == NULL) {
|
||
/*
|
||
* This shouldn't ever happen.
|
||
*/
|
||
|
||
return (char *) "internal error: linked variable couldn't be read";
|
||
}
|
||
|
||
switch (linkPtr->type) {
|
||
case TCL_LINK_INT:
|
||
if (Tcl_GetIntFromObj(NULL, valueObj, &linkPtr->lastValue.i) != TCL_OK
|
||
&& GetInvalidIntFromObj(valueObj, &linkPtr->lastValue.i) != TCL_OK) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have integer value";
|
||
}
|
||
LinkedVar(int) = linkPtr->lastValue.i;
|
||
break;
|
||
|
||
case TCL_LINK_WIDE_INT:
|
||
if (Tcl_GetWideIntFromObj(NULL, valueObj, &linkPtr->lastValue.w) != TCL_OK
|
||
&& GetInvalidWideFromObj(valueObj, &linkPtr->lastValue.w) != TCL_OK) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have integer value";
|
||
}
|
||
LinkedVar(Tcl_WideInt) = linkPtr->lastValue.w;
|
||
break;
|
||
|
||
case TCL_LINK_DOUBLE:
|
||
if (Tcl_GetDoubleFromObj(NULL, valueObj, &linkPtr->lastValue.d) != TCL_OK) {
|
||
#ifdef ACCEPT_NAN
|
||
if (valueObj->typePtr != &tclDoubleType) {
|
||
#endif
|
||
if (GetInvalidDoubleFromObj(valueObj, &linkPtr->lastValue.d) != TCL_OK) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have real value";
|
||
}
|
||
#ifdef ACCEPT_NAN
|
||
}
|
||
linkPtr->lastValue.d = valueObj->internalRep.doubleValue;
|
||
#endif
|
||
}
|
||
LinkedVar(double) = linkPtr->lastValue.d;
|
||
break;
|
||
|
||
case TCL_LINK_BOOLEAN:
|
||
if (Tcl_GetBooleanFromObj(NULL, valueObj, &linkPtr->lastValue.i) != TCL_OK) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have boolean value";
|
||
}
|
||
LinkedVar(int) = linkPtr->lastValue.i;
|
||
break;
|
||
|
||
case TCL_LINK_CHAR:
|
||
if ((Tcl_GetIntFromObj(NULL, valueObj, &valueInt) != TCL_OK
|
||
&& GetInvalidIntFromObj(valueObj, &valueInt) != TCL_OK)
|
||
|| valueInt < SCHAR_MIN || valueInt > SCHAR_MAX) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have char value";
|
||
}
|
||
LinkedVar(char) = linkPtr->lastValue.c = (char)valueInt;
|
||
break;
|
||
|
||
case TCL_LINK_UCHAR:
|
||
if ((Tcl_GetIntFromObj(NULL, valueObj, &valueInt) != TCL_OK
|
||
&& GetInvalidIntFromObj(valueObj, &valueInt) != TCL_OK)
|
||
|| valueInt < 0 || valueInt > UCHAR_MAX) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have unsigned char value";
|
||
}
|
||
LinkedVar(unsigned char) = linkPtr->lastValue.uc = (unsigned char) valueInt;
|
||
break;
|
||
|
||
case TCL_LINK_SHORT:
|
||
if ((Tcl_GetIntFromObj(NULL, valueObj, &valueInt) != TCL_OK
|
||
&& GetInvalidIntFromObj(valueObj, &valueInt) != TCL_OK)
|
||
|| valueInt < SHRT_MIN || valueInt > SHRT_MAX) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have short value";
|
||
}
|
||
LinkedVar(short) = linkPtr->lastValue.s = (short)valueInt;
|
||
break;
|
||
|
||
case TCL_LINK_USHORT:
|
||
if ((Tcl_GetIntFromObj(NULL, valueObj, &valueInt) != TCL_OK
|
||
&& GetInvalidIntFromObj(valueObj, &valueInt) != TCL_OK)
|
||
|| valueInt < 0 || valueInt > USHRT_MAX) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have unsigned short value";
|
||
}
|
||
LinkedVar(unsigned short) = linkPtr->lastValue.us = (unsigned short)valueInt;
|
||
break;
|
||
|
||
case TCL_LINK_UINT:
|
||
if ((Tcl_GetWideIntFromObj(NULL, valueObj, &valueWide) != TCL_OK
|
||
&& GetInvalidWideFromObj(valueObj, &valueWide) != TCL_OK)
|
||
|| valueWide < 0 || valueWide > UINT_MAX) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have unsigned int value";
|
||
}
|
||
LinkedVar(unsigned int) = linkPtr->lastValue.ui = (unsigned int)valueWide;
|
||
break;
|
||
|
||
case TCL_LINK_LONG:
|
||
if ((Tcl_GetWideIntFromObj(NULL, valueObj, &valueWide) != TCL_OK
|
||
&& GetInvalidWideFromObj(valueObj, &valueWide) != TCL_OK)
|
||
|| valueWide < LONG_MIN || valueWide > LONG_MAX) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have long value";
|
||
}
|
||
LinkedVar(long) = linkPtr->lastValue.l = (long)valueWide;
|
||
break;
|
||
|
||
case TCL_LINK_ULONG:
|
||
if ((Tcl_GetWideIntFromObj(NULL, valueObj, &valueWide) != TCL_OK
|
||
&& GetInvalidWideFromObj(valueObj, &valueWide) != TCL_OK)
|
||
|| valueWide < 0 || (Tcl_WideUInt) valueWide > ULONG_MAX) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have unsigned long value";
|
||
}
|
||
LinkedVar(unsigned long) = linkPtr->lastValue.ul = (unsigned long)valueWide;
|
||
break;
|
||
|
||
case TCL_LINK_WIDE_UINT:
|
||
/*
|
||
* FIXME: represent as a bignum.
|
||
*/
|
||
if (Tcl_GetWideIntFromObj(NULL, valueObj, &valueWide) != TCL_OK
|
||
&& GetInvalidWideFromObj(valueObj, &valueWide) != TCL_OK) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have unsigned wide int value";
|
||
}
|
||
LinkedVar(Tcl_WideUInt) = linkPtr->lastValue.uw = (Tcl_WideUInt)valueWide;
|
||
break;
|
||
|
||
case TCL_LINK_FLOAT:
|
||
if ((Tcl_GetDoubleFromObj(NULL, valueObj, &valueDouble) != TCL_OK
|
||
&& GetInvalidDoubleFromObj(valueObj, &valueDouble) != TCL_OK)
|
||
|| valueDouble < -FLT_MAX || valueDouble > FLT_MAX) {
|
||
Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr),
|
||
TCL_GLOBAL_ONLY);
|
||
return (char *) "variable must have float value";
|
||
}
|
||
LinkedVar(float) = linkPtr->lastValue.f = (float)valueDouble;
|
||
break;
|
||
|
||
case TCL_LINK_STRING:
|
||
value = TclGetString(valueObj);
|
||
valueLength = valueObj->length + 1;
|
||
pp = (char **) linkPtr->addr;
|
||
|
||
*pp = ckrealloc(*pp, valueLength);
|
||
memcpy(*pp, value, valueLength);
|
||
break;
|
||
|
||
default:
|
||
return (char *) "internal error: bad linked variable type";
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* ObjValue --
|
||
*
|
||
* Converts the value of a C variable to a Tcl_Obj* for use in a Tcl
|
||
* variable to which it is linked.
|
||
*
|
||
* Results:
|
||
* The return value is a pointer to a Tcl_Obj that represents the value
|
||
* of the C variable given by linkPtr.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static Tcl_Obj *
|
||
ObjValue(
|
||
Link *linkPtr) /* Structure describing linked variable. */
|
||
{
|
||
char *p;
|
||
Tcl_Obj *resultObj;
|
||
|
||
switch (linkPtr->type) {
|
||
case TCL_LINK_INT:
|
||
linkPtr->lastValue.i = LinkedVar(int);
|
||
return Tcl_NewIntObj(linkPtr->lastValue.i);
|
||
case TCL_LINK_WIDE_INT:
|
||
linkPtr->lastValue.w = LinkedVar(Tcl_WideInt);
|
||
return Tcl_NewWideIntObj(linkPtr->lastValue.w);
|
||
case TCL_LINK_DOUBLE:
|
||
linkPtr->lastValue.d = LinkedVar(double);
|
||
return Tcl_NewDoubleObj(linkPtr->lastValue.d);
|
||
case TCL_LINK_BOOLEAN:
|
||
linkPtr->lastValue.i = LinkedVar(int);
|
||
return Tcl_NewBooleanObj(linkPtr->lastValue.i != 0);
|
||
case TCL_LINK_CHAR:
|
||
linkPtr->lastValue.c = LinkedVar(char);
|
||
return Tcl_NewIntObj(linkPtr->lastValue.c);
|
||
case TCL_LINK_UCHAR:
|
||
linkPtr->lastValue.uc = LinkedVar(unsigned char);
|
||
return Tcl_NewIntObj(linkPtr->lastValue.uc);
|
||
case TCL_LINK_SHORT:
|
||
linkPtr->lastValue.s = LinkedVar(short);
|
||
return Tcl_NewIntObj(linkPtr->lastValue.s);
|
||
case TCL_LINK_USHORT:
|
||
linkPtr->lastValue.us = LinkedVar(unsigned short);
|
||
return Tcl_NewIntObj(linkPtr->lastValue.us);
|
||
case TCL_LINK_UINT:
|
||
linkPtr->lastValue.ui = LinkedVar(unsigned int);
|
||
return Tcl_NewWideIntObj((Tcl_WideInt) linkPtr->lastValue.ui);
|
||
case TCL_LINK_LONG:
|
||
linkPtr->lastValue.l = LinkedVar(long);
|
||
return Tcl_NewWideIntObj((Tcl_WideInt) linkPtr->lastValue.l);
|
||
case TCL_LINK_ULONG:
|
||
linkPtr->lastValue.ul = LinkedVar(unsigned long);
|
||
return Tcl_NewWideIntObj((Tcl_WideInt) linkPtr->lastValue.ul);
|
||
case TCL_LINK_FLOAT:
|
||
linkPtr->lastValue.f = LinkedVar(float);
|
||
return Tcl_NewDoubleObj(linkPtr->lastValue.f);
|
||
case TCL_LINK_WIDE_UINT:
|
||
linkPtr->lastValue.uw = LinkedVar(Tcl_WideUInt);
|
||
/*
|
||
* FIXME: represent as a bignum.
|
||
*/
|
||
return Tcl_NewWideIntObj((Tcl_WideInt) linkPtr->lastValue.uw);
|
||
case TCL_LINK_STRING:
|
||
p = LinkedVar(char *);
|
||
if (p == NULL) {
|
||
TclNewLiteralStringObj(resultObj, "NULL");
|
||
return resultObj;
|
||
}
|
||
return Tcl_NewStringObj(p, -1);
|
||
|
||
/*
|
||
* This code only gets executed if the link type is unknown (shouldn't
|
||
* ever happen).
|
||
*/
|
||
|
||
default:
|
||
TclNewLiteralStringObj(resultObj, "??");
|
||
return resultObj;
|
||
}
|
||
}
|
||
|
||
static int SetInvalidRealFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr);
|
||
|
||
static Tcl_ObjType invalidRealType = {
|
||
"invalidReal", /* name */
|
||
NULL, /* freeIntRepProc */
|
||
NULL, /* dupIntRepProc */
|
||
NULL, /* updateStringProc */
|
||
NULL /* setFromAnyProc */
|
||
};
|
||
|
||
static int
|
||
SetInvalidRealFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr) {
|
||
int length;
|
||
const char *str;
|
||
const char *endPtr;
|
||
|
||
str = TclGetStringFromObj(objPtr, &length);
|
||
if ((length == 1) && (str[0] == '.')){
|
||
objPtr->typePtr = &invalidRealType;
|
||
objPtr->internalRep.doubleValue = 0.0;
|
||
return TCL_OK;
|
||
}
|
||
if (TclParseNumber(NULL, objPtr, NULL, str, length, &endPtr,
|
||
TCL_PARSE_DECIMAL_ONLY) == TCL_OK) {
|
||
/* If number is followed by [eE][+-]?, then it is an invalid
|
||
* double, but it could be the start of a valid double. */
|
||
if (*endPtr == 'e' || *endPtr == 'E') {
|
||
++endPtr;
|
||
if (*endPtr == '+' || *endPtr == '-') ++endPtr;
|
||
if (*endPtr == 0) {
|
||
double doubleValue = 0.0;
|
||
Tcl_GetDoubleFromObj(NULL, objPtr, &doubleValue);
|
||
if (objPtr->typePtr->freeIntRepProc) objPtr->typePtr->freeIntRepProc(objPtr);
|
||
objPtr->typePtr = &invalidRealType;
|
||
objPtr->internalRep.doubleValue = doubleValue;
|
||
return TCL_OK;
|
||
}
|
||
}
|
||
}
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
|
||
/*
|
||
* This function checks for integer representations, which are valid
|
||
* when linking with C variables, but which are invalid in other
|
||
* contexts in Tcl. Handled are "+", "-", "", "0x", "0b" and "0o"
|
||
* (upperand lowercase). See bug [39f6304c2e].
|
||
*/
|
||
int
|
||
GetInvalidIntFromObj(Tcl_Obj *objPtr,
|
||
int *intPtr)
|
||
{
|
||
const char *str = TclGetString(objPtr);
|
||
|
||
if ((objPtr->length == 0) ||
|
||
((objPtr->length == 2) && (str[0] == '0') && strchr("xXbBoO", str[1]))) {
|
||
*intPtr = 0;
|
||
return TCL_OK;
|
||
} else if ((objPtr->length == 1) && strchr("+-", str[0])) {
|
||
*intPtr = (str[0] == '+');
|
||
return TCL_OK;
|
||
}
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
int
|
||
GetInvalidWideFromObj(Tcl_Obj *objPtr, Tcl_WideInt *widePtr)
|
||
{
|
||
int intValue;
|
||
|
||
if (GetInvalidIntFromObj(objPtr, &intValue) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
*widePtr = intValue;
|
||
return TCL_OK;
|
||
}
|
||
|
||
/*
|
||
* This function checks for double representations, which are valid
|
||
* when linking with C variables, but which are invalid in other
|
||
* contexts in Tcl. Handled are "+", "-", "", ".", "0x", "0b" and "0o"
|
||
* (upper- and lowercase) and sequences like "1e-". See bug [39f6304c2e].
|
||
*/
|
||
int
|
||
GetInvalidDoubleFromObj(Tcl_Obj *objPtr,
|
||
double *doublePtr)
|
||
{
|
||
int intValue;
|
||
|
||
if (objPtr->typePtr == &invalidRealType) {
|
||
goto gotdouble;
|
||
}
|
||
if (GetInvalidIntFromObj(objPtr, &intValue) == TCL_OK) {
|
||
*doublePtr = (double) intValue;
|
||
return TCL_OK;
|
||
}
|
||
if (SetInvalidRealFromAny(NULL, objPtr) == TCL_OK) {
|
||
gotdouble:
|
||
*doublePtr = objPtr->internalRep.doubleValue;
|
||
return TCL_OK;
|
||
}
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
* Local Variables:
|
||
* mode: c
|
||
* c-basic-offset: 4
|
||
* fill-column: 78
|
||
* End:
|
||
*/
|