/// <summary> TclLookupVar -> lookupVar /// /// This procedure is used by virtually all of the variable /// code to locate a variable given its name(s). /// /// </summary> /// <param name="part1">if part2 isn't NULL, this is the name of an array. /// Otherwise, this is a full variable name that could include /// a parenthesized array elemnt or a scalar. /// </param> /// <param name="part2">Name of an element within array, or null. /// </param> /// <param name="flags">Only the TCL.VarFlag.GLOBAL_ONLY bit matters. /// </param> /// <param name="msg">Verb to use in error messages, e.g. "read" or "set". /// </param> /// <param name="create">OR'ed combination of CRT_PART1 and CRT_PART2. /// Tells which entries to create if they don't already exist. /// </param> /// <param name="throwException">true if an exception should be throw if the /// variable cannot be found. /// </param> /// <returns> a two element array. a[0] is the variable indicated by /// part1 and part2, or null if the variable couldn't be /// found and throwException is false. /// <p> /// If the variable is found, a[1] is the array that /// contains the variable (or null if the variable is a scalar). /// If the variable can't be found and either createPart1 or /// createPart2 are true, a new as-yet-undefined (VAR_UNDEFINED) /// variable instance is created, entered into a hash /// table, and returned. /// Note: it's possible that var.value of the returned variable /// may be null (variable undefined), even if createPart1 or createPart2 /// are true (these only cause the hash table entry or array to be created). /// For example, the variable might be a global that has been unset but /// is still referenced by a procedure, or a variable that has been unset /// but it only being kept in existence by a trace. /// </returns> /// <exception cref=""> TclException if the variable cannot be found and /// throwException is true. /// /// </exception> internal static Var[] lookupVar( Interp interp, string part1, string part2, TCL.VarFlag flags, string msg, bool createPart1, bool createPart2 ) { CallFrame varFrame = interp.varFrame; // Reference to the procedure call frame whose // variables are currently in use. Same as // the current procedure's frame, if any, // unless an "uplevel" is executing. Hashtable table; // to the hashtable, if any, in which // to look up the variable. Var var; // Used to search for global names. string elName; // Name of array element or null. int openParen; // If this procedure parses a name into // array and index, these point to the // parens around the index. Otherwise they // are -1. These are needed to restore // the parens after parsing the name. NamespaceCmd.Namespace varNs, cxtNs; int p; int i, result; var = null; openParen = -1; varNs = null; // set non-null if a nonlocal variable // Parse part1 into array name and index. // Always check if part1 is an array element name and allow it only if // part2 is not given. // (if one does not care about creating array elements that can't be used // from tcl, and prefer slightly better performance, one can put // the following in an if (part2 == null) { ... } block and remove // the part2's test and error reporting or move that code in array set) elName = part2; int len = part1.Length; for ( p = 0; p < len; p++ ) { if ( part1[p] == '(' ) { openParen = p; p = len - 1; if ( part1[p] == ')' ) { if ( (System.Object)part2 != null ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, needArray ); } return null; } elName = part1.Substring( openParen + 1, ( len - 1 ) - ( openParen + 1 ) ); part2 = elName; // same as elName, only used in error reporting part1 = part1.Substring( 0, ( openParen ) - ( 0 ) ); } break; } } // If this namespace has a variable resolver, then give it first // crack at the variable resolution. It may return a Var // value, it may signal to continue onward, or it may signal // an error. if ( ( ( flags & TCL.VarFlag.GLOBAL_ONLY ) != 0 ) || ( interp.varFrame == null ) ) { cxtNs = interp.globalNs; } else { cxtNs = interp.varFrame.ns; } if ( cxtNs.resolver != null || interp.resolvers != null ) { try { if ( cxtNs.resolver != null ) { var = cxtNs.resolver.resolveVar( interp, part1, cxtNs, flags ); } else { var = null; } if ( var == null && interp.resolvers != null ) { IEnumerator enum_Renamed = interp.resolvers.GetEnumerator(); foreach ( Interp.ResolverScheme res in interp.resolvers ) { var = res.resolver.resolveVar( interp, part1, cxtNs, flags ); if ( var != null ) break; } } } catch ( TclException e ) { var = null; } } // Look up part1. Look it up as either a namespace variable or as a // local variable in a procedure call frame (varFrame). // Interpret part1 as a namespace variable if: // 1) so requested by a TCL.VarFlag.GLOBAL_ONLY or TCL.VarFlag.NAMESPACE_ONLY flag, // 2) there is no active frame (we're at the global :: scope), // 3) the active frame was pushed to define the namespace context // for a "namespace eval" or "namespace inscope" command, // 4) the name has namespace qualifiers ("::"s). // Otherwise, if part1 is a local variable, search first in the // frame's array of compiler-allocated local variables, then in its // hashtable for runtime-created local variables. // // If createPart1 and the variable isn't found, create the variable and, // if necessary, create varFrame's local var hashtable. if ( ( ( flags & ( TCL.VarFlag.GLOBAL_ONLY | TCL.VarFlag.NAMESPACE_ONLY ) ) != 0 ) || ( varFrame == null ) || !varFrame.isProcCallFrame || ( part1.IndexOf( "::" ) != -1 ) ) { string tail; // Don't pass TCL.VarFlag.LEAVE_ERR_MSG, we may yet create the variable, // or otherwise generate our own error! var = NamespaceCmd.findNamespaceVar( interp, part1, null, flags & ~TCL.VarFlag.LEAVE_ERR_MSG ); if ( var == null ) { if ( createPart1 ) { // var wasn't found so create it // Java does not support passing an address so we pass // an array of size 1 and then assign arr[0] to the value NamespaceCmd.Namespace[] varNsArr = new NamespaceCmd.Namespace[1]; NamespaceCmd.Namespace[] dummyArr = new NamespaceCmd.Namespace[1]; string[] tailArr = new string[1]; NamespaceCmd.getNamespaceForQualName( interp, part1, null, flags, varNsArr, dummyArr, dummyArr, tailArr ); // Get the values out of the arrays! varNs = varNsArr[0]; tail = tailArr[0]; if ( varNs == null ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, badNamespace ); } return null; } if ( (System.Object)tail == null ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, missingName ); } return null; } var = new Var(); varNs.varTable.Add( tail, var ); // There is no hPtr member in Jacl, The hPtr combines the table // and the key used in a table lookup. var.hashKey = tail; var.table = varNs.varTable; var.ns = varNs; } else { // var wasn't found and not to create it if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, noSuchVar ); } return null; } } } else { // local var: look in frame varFrame // removed code block that searches for local compiled vars if ( var == null ) { // look in the frame's var hash table table = varFrame.varTable; if ( createPart1 ) { if ( table == null ) { table = new Hashtable(); varFrame.varTable = table; } var = (Var)table[part1]; if ( var == null ) { // we are adding a new entry var = new Var(); SupportClass.PutElement( table, part1, var ); // There is no hPtr member in Jacl, The hPtr combines // the table and the key used in a table lookup. var.hashKey = part1; var.table = table; var.ns = null; // a local variable } } else { if ( table != null ) { var = (Var)table[part1]; } if ( var == null ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, noSuchVar ); } return null; } } } } // If var is a link variable, we have a reference to some variable // that was created through an "upvar" or "global" command. Traverse // through any links until we find the referenced variable. while ( var.isVarLink() ) { var = (Var)var.value; } // If we're not dealing with an array element, return var. if ( (System.Object)elName == null ) { var ret = new Var[2]; ret[0] = var; ret[1] = null; return ret; } // We're dealing with an array element. Make sure the variable is an // array and look up the element (create the element if desired). if ( var.isVarUndefined() && !var.isVarArrayElement() ) { if ( !createPart1 ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, noSuchVar ); } return null; } // Make sure we are not resurrecting a namespace variable from a // deleted namespace! if ( ( ( var.flags & VarFlags.IN_HASHTABLE ) != 0 ) && ( var.table == null ) ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, danglingVar ); } return null; } var.setVarArray(); var.clearVarUndefined(); var.value = new Hashtable(); } else if ( !var.isVarArray() ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, needArray ); } return null; } Var arrayVar = var; Hashtable arrayTable = (Hashtable)var.value; if ( createPart2 ) { Var searchvar = (Var)arrayTable[elName]; if ( searchvar == null ) { // new entry if ( var.sidVec != null ) { deleteSearches( var ); } var = new Var(); SupportClass.PutElement( arrayTable, elName, var ); // There is no hPtr member in Jacl, The hPtr combines the table // and the key used in a table lookup. var.hashKey = elName; var.table = arrayTable; var.ns = varNs; var.setVarArrayElement(); } else { var = searchvar; } } else { var = (Var)arrayTable[elName]; if ( var == null ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, msg, noSuchElement ); } return null; } } var ret2 = new Var[2]; ret2[0] = var; // The Var in the array ret2[1] = arrayVar; // The array (Hashtable) Var return ret2; }
/// <summary> DeleteArray -> deleteArray /// /// This procedure is called to free up everything in an array /// variable. It's the caller's responsibility to make sure /// that the array is no longer accessible before this procedure /// is called. /// /// </summary> /// <param name="interp">Interpreter containing array. /// </param> /// <param name="arrayName">name of array (used for trace callbacks). /// </param> /// <param name="var">the array variable to delete. /// </param> /// <param name="flags">Flags to pass to CallTraces. /// </param> static protected internal void deleteArray( Interp interp, string arrayName, Var var, TCL.VarFlag flags ) { IEnumerator search; Var el; TclObject obj; deleteSearches( var ); Hashtable table = (Hashtable)var.value; Var dummyVar; for ( search = table.Values.GetEnumerator(); search.MoveNext(); ) { el = (Var)search.Current; if ( el.isVarScalar() && ( el.value != null ) ) { obj = (TclObject)el.value; obj.release(); el.value = null; } string tmpkey = (string)el.hashKey; // There is no hPtr member in Jacl, The hPtr combines the table // and the key used in a table lookup. el.hashKey = null; el.table = null; if ( el.traces != null ) { el.flags &= ~VarFlags.TRACE_ACTIVE; // FIXME : Old Jacl impl passed a dummy var to callTraces, should we? callTraces( interp, null, el, arrayName, tmpkey, flags ); el.traces = null; // Active trace stuff is not part of Jacl } el.setVarUndefined(); el.setVarScalar(); if ( el.refCount == 0 ) { // We are no longer using the element // element Vars are IN_HASHTABLE } } ( (Hashtable)var.value ).Clear(); var.value = null; }
/// <summary> CleanupVar -> cleanupVar /// /// This procedure is called when it looks like it may be OK /// to free up the variable's record and hash table entry, and /// those of its containing parent. It's called, for example, /// when a trace on a variable deletes the variable. /// /// </summary> /// <param name="var">variable that may be a candidate for being expunged. /// </param> /// <param name="array">Array that contains the variable, or NULL if this /// variable isn't an array element. /// </param> static protected internal void cleanupVar( Var var, Var array ) { if ( var.isVarUndefined() && ( var.refCount == 0 ) && ( var.traces == null ) && ( ( var.flags & VarFlags.IN_HASHTABLE ) != 0 ) ) { if ( var.table != null ) { SupportClass.HashtableRemove( var.table, var.hashKey ); var.table = null; var.hashKey = null; } } if ( array != null ) { if ( array.isVarUndefined() && ( array.refCount == 0 ) && ( array.traces == null ) && ( ( array.flags & VarFlags.IN_HASHTABLE ) != 0 ) ) { if ( array.table != null ) { SupportClass.HashtableRemove( array.table, array.hashKey ); array.table = null; array.hashKey = null; } } } }
/// <summary> DeleteSearches -> deleteSearches /// /// This procedure is called to free up all of the searches /// associated with an array variable. /// /// </summary> /// <param name="interp">Interpreter containing array. /// </param> /// <param name="arrayVar">the array variable to delete searches from. /// </param> static protected internal void deleteSearches( Var arrayVar ) // Variable whose searches are to be deleted. { arrayVar.sidVec = null; }
/// <summary> CallTraces -> callTraces /// /// This procedure is invoked to find and invoke relevant /// trace procedures associated with a particular operation on /// a variable. This procedure invokes traces both on the /// variable and on its containing array (where relevant). /// /// </summary> /// <param name="interp">Interpreter containing variable. /// </param> /// <param name="array">array variable that contains the variable, or null /// if the variable isn't an element of an array. /// </param> /// <param name="var">Variable whose traces are to be invoked. /// </param> /// <param name="part1">the first part of a variable name. /// </param> /// <param name="part2">the second part of a variable name. /// </param> /// <param name="flags">Flags to pass to trace procedures: indicates /// what's happening to variable, plus other stuff like /// TCL.VarFlag.GLOBAL_ONLY, TCL.VarFlag.NAMESPACE_ONLY, and TCL.VarFlag.INTERP_DESTROYED. /// </param> /// <returns> null if no trace procedures were invoked, or /// if all the invoked trace procedures returned successfully. /// The return value is non-null if a trace procedure returned an /// error (in this case no more trace procedures were invoked /// after the error was returned). In this case the return value /// is a pointer to a string describing the error. /// </returns> static protected internal string callTraces( Interp interp, Var array, Var var, string part1, string part2, TCL.VarFlag flags ) { TclObject oldResult; int i; // If there are already similar trace procedures active for the // variable, don't call them again. if ( ( var.flags & VarFlags.TRACE_ACTIVE ) != 0 ) { return null; } var.flags |= VarFlags.TRACE_ACTIVE; var.refCount++; // If the variable name hasn't been parsed into array name and // element, do it here. If there really is an array element, // make a copy of the original name so that nulls can be // inserted into it to separate the names (can't modify the name // string in place, because the string might get used by the // callbacks we invoke). // FIXME : come up with parsing code to use for all situations! if ( (System.Object)part2 == null ) { int len = part1.Length; if ( len > 0 ) { if ( part1[len - 1] == ')' ) { for ( i = 0; i < len - 1; i++ ) { if ( part1[i] == '(' ) { break; } } if ( i < len - 1 ) { if ( i < len - 2 ) { part2 = part1.Substring( i + 1, ( len - 1 ) - ( i + 1 ) ); part1 = part1.Substring( 0, ( i ) - ( 0 ) ); } } } } } oldResult = interp.getResult(); oldResult.preserve(); interp.resetResult(); try { // Invoke traces on the array containing the variable, if relevant. if ( array != null ) { array.refCount++; } if ( ( array != null ) && ( array.traces != null ) ) { for ( i = 0; ( array.traces != null ) && ( i < array.traces.Count ); i++ ) { TraceRecord rec = (TraceRecord)array.traces[i]; if ( ( rec.flags & flags ) != 0 ) { try { rec.trace.traceProc( interp, part1, part2, flags ); } catch ( TclException e ) { if ( ( flags & TCL.VarFlag.TRACE_UNSETS ) == 0 ) { return interp.getResult().ToString(); } } } } } // Invoke traces on the variable itself. if ( ( flags & TCL.VarFlag.TRACE_UNSETS ) != 0 ) { flags |= TCL.VarFlag.TRACE_DESTROYED; } for ( i = 0; ( var.traces != null ) && ( i < var.traces.Count ); i++ ) { TraceRecord rec = (TraceRecord)var.traces[i]; if ( ( rec.flags & flags ) != 0 ) { try { rec.trace.traceProc( interp, part1, part2, flags ); } catch ( TclException e ) { if ( ( flags & TCL.VarFlag.TRACE_UNSETS ) == 0 ) { return interp.getResult().ToString(); } } } } return null; } finally { if ( array != null ) { array.refCount--; } var.flags &= ~VarFlags.TRACE_ACTIVE; var.refCount--; interp.setResult( oldResult ); oldResult.release(); } }
/* *---------------------------------------------------------------------- * * TCL.Tcl_GetVariableFullName -> getVariableFullName * * Given a Var token returned by NamespaceCmd.FindNamespaceVar, this * procedure appends to an object the namespace variable's full * name, qualified by a sequence of parent namespace names. * * Results: * None. * * Side effects: * The variable's fully-qualified name is returned. * *---------------------------------------------------------------------- */ internal static string getVariableFullName( Interp interp, Var var ) { StringBuilder buff = new StringBuilder(); // Add the full name of the containing namespace (if any), followed by // the "::" separator, then the variable name. if ( var != null ) { if ( !var.isVarArrayElement() ) { if ( var.ns != null ) { buff.Append( var.ns.fullName ); if ( var.ns != interp.globalNs ) { buff.Append( "::" ); } } // Jacl's Var class does not include the "name" member // We use the "hashKey" member which is equivalent if ( (System.Object)var.hashKey != null ) { buff.Append( var.hashKey ); } } } return buff.ToString(); }
/// <summary> MakeUpvar -> makeUpvar /// /// Create a reference of a variable in otherFrame in the current /// CallFrame, given a two-part name consisting of array name and /// element within array. /// /// </summary> /// <param name="interp">Interp containing the variables /// </param> /// <param name="frame">CallFrame containing "other" variable. /// null means use global context. /// </param> /// <param name="otherP1">the 1st part name of the variable in the "other" frame. /// </param> /// <param name="otherP2">the 2nd part name of the variable in the "other" frame. /// </param> /// <param name="otherFlags">the flags for scaope of "other" variable /// </param> /// <param name="myName">Name of scalar variable which will refer to otherP1/otherP2. /// </param> /// <param name="myFlags">only the TCL.VarFlag.GLOBAL_ONLY bit matters, /// indicating the scope of myName. /// </param> /// <exception cref=""> TclException if the upvar cannot be created. /// </exception> protected internal static void makeUpvar( Interp interp, CallFrame frame, string otherP1, string otherP2, TCL.VarFlag otherFlags, string myName, TCL.VarFlag myFlags ) { Var other, var, array; Var[] result; CallFrame varFrame; CallFrame savedFrame = null; Hashtable table; NamespaceCmd.Namespace ns, altNs; string tail; bool newvar = false; // Find "other" in "frame". If not looking up other in just the // current namespace, temporarily replace the current var frame // pointer in the interpreter in order to use TclLookupVar. if ( ( otherFlags & TCL.VarFlag.NAMESPACE_ONLY ) == 0 ) { savedFrame = interp.varFrame; interp.varFrame = frame; } result = lookupVar( interp, otherP1, otherP2, ( otherFlags | TCL.VarFlag.LEAVE_ERR_MSG ), "access", true, true ); if ( ( otherFlags & TCL.VarFlag.NAMESPACE_ONLY ) == 0 ) { interp.varFrame = savedFrame; } other = result[0]; array = result[1]; if ( other == null ) { // FIXME : leave error message thing again throw new TclRuntimeError( "unexpected null reference" ); } // Now create a hashtable entry for "myName". Create it as either a // namespace variable or as a local variable in a procedure call // frame. Interpret myName as a namespace variable if: // 1) so requested by a TCL.VarFlag.GLOBAL_ONLY or TCL.VarFlag.NAMESPACE_ONLY flag, // 2) there is no active frame (we're at the global :: scope), // 3) the active frame was pushed to define the namespace context // for a "namespace eval" or "namespace inscope" command, // 4) the name has namespace qualifiers ("::"s). // If creating myName in the active procedure, look in its // hashtable for runtime-created local variables. Create that // procedure's local variable hashtable if necessary. varFrame = interp.varFrame; if ( ( ( myFlags & ( TCL.VarFlag.GLOBAL_ONLY | TCL.VarFlag.NAMESPACE_ONLY ) ) != 0 ) || ( varFrame == null ) || !varFrame.isProcCallFrame || ( myName.IndexOf( "::" ) != -1 ) ) { // Java does not support passing an address so we pass // an array of size 1 and then assign arr[0] to the value NamespaceCmd.Namespace[] nsArr = new NamespaceCmd.Namespace[1]; NamespaceCmd.Namespace[] altNsArr = new NamespaceCmd.Namespace[1]; NamespaceCmd.Namespace[] dummyNsArr = new NamespaceCmd.Namespace[1]; string[] tailArr = new string[1]; NamespaceCmd.getNamespaceForQualName( interp, myName, null, myFlags, nsArr, altNsArr, dummyNsArr, tailArr ); // Get the values out of the arrays! ns = nsArr[0]; altNs = altNsArr[0]; tail = tailArr[0]; if ( ns == null ) { ns = altNs; } if ( ns == null ) { throw new TclException( interp, "bad variable name \"" + myName + "\": unknown namespace" ); } // Check that we are not trying to create a namespace var linked to // a local variable in a procedure. If we allowed this, the local // variable in the shorter-lived procedure frame could go away // leaving the namespace var's reference invalid. if ( ( ( (System.Object)otherP2 != null ) ? array.ns : other.ns ) == null ) { throw new TclException( interp, "bad variable name \"" + myName + "\": upvar won't create namespace variable that refers to procedure variable" ); } // AKT var = (Var) ns.varTable.get(tail); var = (Var)ns.varTable[tail]; if ( var == null ) { // we are adding a new entry newvar = true; var = new Var(); // ATK ns.varTable.put(tail, var); ns.varTable.Add( tail, var ); // There is no hPtr member in Jacl, The hPtr combines the table // and the key used in a table lookup. var.hashKey = tail; var.table = ns.varTable; var.ns = ns; } } else { // Skip Compiled Local stuff var = null; if ( var == null ) { // look in frame's local var hashtable table = varFrame.varTable; if ( table == null ) { table = new Hashtable(); varFrame.varTable = table; } var = (Var)table[myName]; if ( var == null ) { // we are adding a new entry newvar = true; var = new Var(); SupportClass.PutElement( table, myName, var ); // There is no hPtr member in Jacl, The hPtr combines the table // and the key used in a table lookup. var.hashKey = myName; var.table = table; var.ns = varFrame.ns; } } } if ( !newvar ) { // The variable already exists. Make sure this variable "var" // isn't the same as "other" (avoid circular links). Also, if // it's not an upvar then it's an error. If it is an upvar, then // just disconnect it from the thing it currently refers to. if ( var == other ) { throw new TclException( interp, "can't upvar from variable to itself" ); } if ( var.isVarLink() ) { Var link = (Var)var.value; if ( link == other ) { return; } link.refCount--; if ( link.isVarUndefined() ) { cleanupVar( link, null ); } } else if ( !var.isVarUndefined() ) { throw new TclException( interp, "variable \"" + myName + "\" already exists" ); } else if ( var.traces != null ) { throw new TclException( interp, "variable \"" + myName + "\" has traces: can't use for upvar" ); } } var.setVarLink(); var.clearVarUndefined(); var.value = other; other.refCount++; return; }
/// <summary> TCL.Tcl_UnsetVar2 -> unsetVar /// /// Unset a variable, given a two-part name consisting of array /// name and element within array. /// /// </summary> /// <param name="part1">1st part of the variable name. /// </param> /// <param name="part2">2nd part of the variable name. /// </param> /// <param name="flags">misc flags that control the actions of this method. /// /// If part1 and part2 indicate a local or global variable in interp, /// it is deleted. If part1 is an array name and part2 is null, then /// the whole array is deleted. /// /// </param> internal static void unsetVar( Interp interp, string part1, string part2, TCL.VarFlag flags ) { Var dummyVar; Var var; Var array; //ActiveVarTrace active; TclObject obj; TCL.CompletionCode result; // FIXME : what about the null return vs exception thing here? Var[] lookup_result = lookupVar( interp, part1, part2, flags, "unset", false, false ); if ( lookup_result == null ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) throw new TclRuntimeError( "unexpected null reference" ); else return; } var = lookup_result[0]; array = lookup_result[1]; result = ( var.isVarUndefined() ? TCL.CompletionCode.ERROR : TCL.CompletionCode.OK ); if ( ( array != null ) && ( array.sidVec != null ) ) { deleteSearches( array ); } // The code below is tricky, because of the possibility that // a trace procedure might try to access a variable being // deleted. To handle this situation gracefully, do things // in three steps: // 1. Copy the contents of the variable to a dummy variable // structure, and mark the original Var structure as undefined. // 2. Invoke traces and clean up the variable, using the dummy copy. // 3. If at the end of this the original variable is still // undefined and has no outstanding references, then delete // it (but it could have gotten recreated by a trace). dummyVar = new Var(); //FIXME: Var class really should implement clone to make a bit copy. dummyVar.value = var.value; dummyVar.traces = var.traces; dummyVar.flags = var.flags; dummyVar.hashKey = var.hashKey; dummyVar.table = var.table; dummyVar.refCount = var.refCount; dummyVar.ns = var.ns; var.setVarUndefined(); var.setVarScalar(); var.value = null; // dummyVar points to any value object var.traces = null; var.sidVec = null; // Call trace procedures for the variable being deleted. Then delete // its traces. Be sure to abort any other traces for the variable // that are still pending. Special tricks: // 1. We need to increment var's refCount around this: CallTraces // will use dummyVar so it won't increment var's refCount itself. // 2. Turn off the TRACE_ACTIVE flag in dummyVar: we want to // call unset traces even if other traces are pending. if ( ( dummyVar.traces != null ) || ( ( array != null ) && ( array.traces != null ) ) ) { var.refCount++; dummyVar.flags &= ~VarFlags.TRACE_ACTIVE; callTraces( interp, array, dummyVar, part1, part2, ( flags & ( TCL.VarFlag.GLOBAL_ONLY | TCL.VarFlag.NAMESPACE_ONLY ) ) | TCL.VarFlag.TRACE_UNSETS ); dummyVar.traces = null; // Active trace stuff is not part of Jacl's interp var.refCount--; } // If the variable is an array, delete all of its elements. This must be // done after calling the traces on the array, above (that's the way // traces are defined). If it is a scalar, "discard" its object // (decrement the ref count of its object, if any). if ( dummyVar.isVarArray() && !dummyVar.isVarUndefined() ) { deleteArray( interp, part1, dummyVar, ( flags & ( TCL.VarFlag.GLOBAL_ONLY | TCL.VarFlag.NAMESPACE_ONLY ) ) | TCL.VarFlag.TRACE_UNSETS ); } if ( dummyVar.isVarScalar() && ( dummyVar.value != null ) ) { obj = (TclObject)dummyVar.value; obj.release(); dummyVar.value = null; } // If the variable was a namespace variable, decrement its reference count. if ( ( var.flags & VarFlags.NAMESPACE_VAR ) != 0 ) { var.flags &= ~VarFlags.NAMESPACE_VAR; var.refCount--; } // Finally, if the variable is truly not in use then free up its Var // structure and remove it from its hash table, if any. The ref count of // its value object, if any, was decremented above. cleanupVar( var, array ); // It's an error to unset an undefined variable. if ( result != TCL.CompletionCode.OK ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, "unset", ( ( array == null ) ? noSuchVar : noSuchElement ) ); } } }