/*1* tclLink.c --2*3* This file implements linked variables (a C variable that is4* tied to a Tcl variable). The idea of linked variables was5* first suggested by Andreas Stolcke and this implementation is6* based heavily on a prototype implementation provided by7* him.8*9* Copyright (c) 1993 The Regents of the University of California.10* Copyright (c) 1994-1996 Sun Microsystems, Inc.11*12* See the file "license.terms" for information on usage and redistribution13* of this file, and for a DISCLAIMER OF ALL WARRANTIES.14*15* SCCS: @(#) tclLink.c 1.13 96/08/09 16:23:3416*/1718#include "tclInt.h"1920/*21* For each linked variable there is a data structure of the following22* type, which describes the link and is the clientData for the trace23* set on the Tcl variable.24*/2526typedef struct Link {27Tcl_Interp *interp; /* Interpreter containing Tcl variable. */28char *varName; /* Name of variable (must be global). This29* is needed during trace callbacks, since30* the actual variable may be aliased at31* that time via upvar. */32char *addr; /* Location of C variable. */33int type; /* Type of link (TCL_LINK_INT, etc.). */34union {35int i;36double d;37} lastValue; /* Last known value of C variable; used to38* avoid string conversions. */39int flags; /* Miscellaneous one-bit values; see below40* for definitions. */41} Link;4243/*44* Definitions for flag bits:45* LINK_READ_ONLY - 1 means errors should be generated if Tcl46* script attempts to write variable.47* LINK_BEING_UPDATED - 1 means that a call to Tcl_UpdateLinkedVar48* is in progress for this variable, so49* trace callbacks on the variable should50* be ignored.51*/5253#define LINK_READ_ONLY 154#define LINK_BEING_UPDATED 25556/*57* Forward references to procedures defined later in this file:58*/5960static char * LinkTraceProc _ANSI_ARGS_((ClientData clientData,61Tcl_Interp *interp, char *name1, char *name2,62int flags));63static char * StringValue _ANSI_ARGS_((Link *linkPtr,64char *buffer));6566/*67*----------------------------------------------------------------------68*69* Tcl_LinkVar --70*71* Link a C variable to a Tcl variable so that changes to either72* one causes the other to change.73*74* Results:75* The return value is TCL_OK if everything went well or TCL_ERROR76* if an error occurred (interp->result is also set after errors).77*78* Side effects:79* The value at *addr is linked to the Tcl variable "varName",80* using "type" to convert between string values for Tcl and81* binary values for *addr.82*83*----------------------------------------------------------------------84*/8586int87Tcl_LinkVar(interp, varName, addr, type)88Tcl_Interp *interp; /* Interpreter in which varName exists. */89char *varName; /* Name of a global variable in interp. */90char *addr; /* Address of a C variable to be linked91* to varName. */92int type; /* Type of C variable: TCL_LINK_INT, etc.93* Also may have TCL_LINK_READ_ONLY94* OR'ed in. */95{96Link *linkPtr;97char buffer[TCL_DOUBLE_SPACE];98int code;99100linkPtr = (Link *) ckalloc(sizeof(Link));101linkPtr->interp = interp;102linkPtr->varName = (char *) ckalloc((unsigned) (strlen(varName) + 1));103strcpy(linkPtr->varName, varName);104linkPtr->addr = addr;105linkPtr->type = type & ~TCL_LINK_READ_ONLY;106if (type & TCL_LINK_READ_ONLY) {107linkPtr->flags = LINK_READ_ONLY;108} else {109linkPtr->flags = 0;110}111if (Tcl_SetVar(interp, varName, StringValue(linkPtr, buffer),112TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {113ckfree(linkPtr->varName);114ckfree((char *) linkPtr);115return TCL_ERROR;116}117code = Tcl_TraceVar(interp, varName, TCL_GLOBAL_ONLY|TCL_TRACE_READS118|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, LinkTraceProc,119(ClientData) linkPtr);120if (code != TCL_OK) {121ckfree(linkPtr->varName);122ckfree((char *) linkPtr);123}124return code;125}126127/*128*----------------------------------------------------------------------129*130* Tcl_UnlinkVar --131*132* Destroy the link between a Tcl variable and a C variable.133*134* Results:135* None.136*137* Side effects:138* If "varName" was previously linked to a C variable, the link139* is broken to make the variable independent. If there was no140* previous link for "varName" then nothing happens.141*142*----------------------------------------------------------------------143*/144145void146Tcl_UnlinkVar(interp, varName)147Tcl_Interp *interp; /* Interpreter containing variable to unlink. */148char *varName; /* Global variable in interp to unlink. */149{150Link *linkPtr;151152linkPtr = (Link *) Tcl_VarTraceInfo(interp, varName, TCL_GLOBAL_ONLY,153LinkTraceProc, (ClientData) NULL);154if (linkPtr == NULL) {155return;156}157Tcl_UntraceVar(interp, varName,158TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,159LinkTraceProc, (ClientData) linkPtr);160ckfree(linkPtr->varName);161ckfree((char *) linkPtr);162}163164/*165*----------------------------------------------------------------------166*167* Tcl_UpdateLinkedVar --168*169* This procedure is invoked after a linked variable has been170* changed by C code. It updates the Tcl variable so that171* traces on the variable will trigger.172*173* Results:174* None.175*176* Side effects:177* The Tcl variable "varName" is updated from its C value,178* causing traces on the variable to trigger.179*180*----------------------------------------------------------------------181*/182183void184Tcl_UpdateLinkedVar(interp, varName)185Tcl_Interp *interp; /* Interpreter containing variable. */186char *varName; /* Name of global variable that is linked. */187{188Link *linkPtr;189char buffer[TCL_DOUBLE_SPACE];190int savedFlag;191192linkPtr = (Link *) Tcl_VarTraceInfo(interp, varName, TCL_GLOBAL_ONLY,193LinkTraceProc, (ClientData) NULL);194if (linkPtr == NULL) {195return;196}197savedFlag = linkPtr->flags & LINK_BEING_UPDATED;198linkPtr->flags |= LINK_BEING_UPDATED;199Tcl_SetVar(interp, linkPtr->varName, StringValue(linkPtr, buffer),200TCL_GLOBAL_ONLY);201linkPtr->flags = (linkPtr->flags & ~LINK_BEING_UPDATED) | savedFlag;202}203204/*205*----------------------------------------------------------------------206*207* LinkTraceProc --208*209* This procedure is invoked when a linked Tcl variable is read,210* written, or unset from Tcl. It's responsible for keeping the211* C variable in sync with the Tcl variable.212*213* Results:214* If all goes well, NULL is returned; otherwise an error message215* is returned.216*217* Side effects:218* The C variable may be updated to make it consistent with the219* Tcl variable, or the Tcl variable may be overwritten to reject220* a modification.221*222*----------------------------------------------------------------------223*/224225static char *226LinkTraceProc(clientData, interp, name1, name2, flags)227ClientData clientData; /* Contains information about the link. */228Tcl_Interp *interp; /* Interpreter containing Tcl variable. */229char *name1; /* First part of variable name. */230char *name2; /* Second part of variable name. */231int flags; /* Miscellaneous additional information. */232{233Link *linkPtr = (Link *) clientData;234int changed;235char buffer[TCL_DOUBLE_SPACE];236char *value, **pp;237Tcl_DString savedResult;238239/*240* If the variable is being unset, then just re-create it (with a241* trace) unless the whole interpreter is going away.242*/243244if (flags & TCL_TRACE_UNSETS) {245if (flags & TCL_INTERP_DESTROYED) {246ckfree(linkPtr->varName);247ckfree((char *) linkPtr);248} else if (flags & TCL_TRACE_DESTROYED) {249Tcl_SetVar(interp, linkPtr->varName, StringValue(linkPtr, buffer),250TCL_GLOBAL_ONLY);251Tcl_TraceVar(interp, linkPtr->varName, TCL_GLOBAL_ONLY252|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,253LinkTraceProc, (ClientData) linkPtr);254}255return NULL;256}257258/*259* If we were invoked because of a call to Tcl_UpdateLinkedVar, then260* don't do anything at all. In particular, we don't want to get261* upset that the variable is being modified, even if it is262* supposed to be read-only.263*/264265if (linkPtr->flags & LINK_BEING_UPDATED) {266return NULL;267}268269/*270* For read accesses, update the Tcl variable if the C variable271* has changed since the last time we updated the Tcl variable.272*/273274if (flags & TCL_TRACE_READS) {275switch (linkPtr->type) {276case TCL_LINK_INT:277case TCL_LINK_BOOLEAN:278changed = *(int *)(linkPtr->addr) != linkPtr->lastValue.i;279break;280case TCL_LINK_DOUBLE:281changed = *(double *)(linkPtr->addr) != linkPtr->lastValue.d;282break;283case TCL_LINK_STRING:284changed = 1;285break;286default:287return "internal error: bad linked variable type";288}289if (changed) {290Tcl_SetVar(interp, linkPtr->varName, StringValue(linkPtr, buffer),291TCL_GLOBAL_ONLY);292}293return NULL;294}295296/*297* For writes, first make sure that the variable is writable. Then298* convert the Tcl value to C if possible. If the variable isn't299* writable or can't be converted, then restore the varaible's old300* value and return an error. Another tricky thing: we have to save301* and restore the interpreter's result, since the variable access302* could occur when the result has been partially set.303*/304305if (linkPtr->flags & LINK_READ_ONLY) {306Tcl_SetVar(interp, linkPtr->varName, StringValue(linkPtr, buffer),307TCL_GLOBAL_ONLY);308return "linked variable is read-only";309}310value = Tcl_GetVar(interp, linkPtr->varName, TCL_GLOBAL_ONLY);311if (value == NULL) {312/*313* This shouldn't ever happen.314*/315return "internal error: linked variable couldn't be read";316}317Tcl_DStringInit(&savedResult);318Tcl_DStringAppend(&savedResult, interp->result, -1);319Tcl_ResetResult(interp);320switch (linkPtr->type) {321case TCL_LINK_INT:322if (Tcl_GetInt(interp, value, &linkPtr->lastValue.i) != TCL_OK) {323Tcl_DStringResult(interp, &savedResult);324Tcl_SetVar(interp, linkPtr->varName,325StringValue(linkPtr, buffer), TCL_GLOBAL_ONLY);326return "variable must have integer value";327}328*(int *)(linkPtr->addr) = linkPtr->lastValue.i;329break;330case TCL_LINK_DOUBLE:331if (Tcl_GetDouble(interp, value, &linkPtr->lastValue.d)332!= TCL_OK) {333Tcl_DStringResult(interp, &savedResult);334Tcl_SetVar(interp, linkPtr->varName,335StringValue(linkPtr, buffer), TCL_GLOBAL_ONLY);336return "variable must have real value";337}338*(double *)(linkPtr->addr) = linkPtr->lastValue.d;339break;340case TCL_LINK_BOOLEAN:341if (Tcl_GetBoolean(interp, value, &linkPtr->lastValue.i)342!= TCL_OK) {343Tcl_DStringResult(interp, &savedResult);344Tcl_SetVar(interp, linkPtr->varName,345StringValue(linkPtr, buffer), TCL_GLOBAL_ONLY);346return "variable must have boolean value";347}348*(int *)(linkPtr->addr) = linkPtr->lastValue.i;349break;350case TCL_LINK_STRING:351pp = (char **)(linkPtr->addr);352if (*pp != NULL) {353ckfree(*pp);354}355*pp = (char *) ckalloc((unsigned) (strlen(value) + 1));356strcpy(*pp, value);357break;358default:359return "internal error: bad linked variable type";360}361Tcl_DStringResult(interp, &savedResult);362return NULL;363}364365/*366*----------------------------------------------------------------------367*368* StringValue --369*370* Converts the value of a C variable to a string for use in a371* Tcl variable to which it is linked.372*373* Results:374* The return value is a pointer375to a string that represents376* the value of the C variable given by linkPtr.377*378* Side effects:379* None.380*381*----------------------------------------------------------------------382*/383384static char *385StringValue(linkPtr, buffer)386Link *linkPtr; /* Structure describing linked variable. */387char *buffer; /* Small buffer to use for converting388* values. Must have TCL_DOUBLE_SPACE389* bytes or more. */390{391char *p;392393switch (linkPtr->type) {394case TCL_LINK_INT:395linkPtr->lastValue.i = *(int *)(linkPtr->addr);396sprintf(buffer, "%d", linkPtr->lastValue.i);397return buffer;398case TCL_LINK_DOUBLE:399linkPtr->lastValue.d = *(double *)(linkPtr->addr);400Tcl_PrintDouble(linkPtr->interp, linkPtr->lastValue.d, buffer);401return buffer;402case TCL_LINK_BOOLEAN:403linkPtr->lastValue.i = *(int *)(linkPtr->addr);404if (linkPtr->lastValue.i != 0) {405return "1";406}407return "0";408case TCL_LINK_STRING:409p = *(char **)(linkPtr->addr);410if (p == NULL) {411return "NULL";412}413return p;414}415416/*417* This code only gets executed if the link type is unknown418* (shouldn't ever happen).419*/420421return "??";422}423424425