]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * tclVar.c -- | |
3 | * | |
4 | * This file contains routines that implement Tcl variables | |
5 | * (both scalars and arrays). | |
6 | * | |
7 | * The implementation of arrays is modelled after an initial | |
8 | * implementation by Karl Lehenbauer, Mark Diekhans and | |
9 | * Peter da Silva. | |
10 | * | |
11 | * Copyright 1987-1991 Regents of the University of California | |
12 | * Permission to use, copy, modify, and distribute this | |
13 | * software and its documentation for any purpose and without | |
14 | * fee is hereby granted, provided that the above copyright | |
15 | * notice appear in all copies. The University of California | |
16 | * makes no representations about the suitability of this | |
17 | * software for any purpose. It is provided "as is" without | |
18 | * express or implied warranty. | |
19 | */ | |
20 | ||
21 | #ifndef lint | |
22 | static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclVar.c,v 1.27 92/05/07 09:24:59 ouster Exp $ SPRITE (Berkeley)"; | |
23 | #endif | |
24 | ||
25 | #include "tclint.h" | |
26 | ||
27 | /* | |
28 | * The strings below are used to indicate what went wrong when a | |
29 | * variable access is denied. | |
30 | */ | |
31 | ||
32 | static char *noSuchVar = "no such variable"; | |
33 | static char *isArray = "variable is array"; | |
34 | static char *needArray = "variable isn't array"; | |
35 | static char *noSuchElement = "no such element in array"; | |
36 | static char *traceActive = "trace is active on variable"; | |
37 | ||
38 | /* | |
39 | * Forward references to procedures defined later in this file: | |
40 | */ | |
41 | ||
42 | static char * CallTraces _ANSI_ARGS_((Interp *iPtr, Var *arrayPtr, | |
43 | Tcl_HashEntry *hPtr, char *name1, char *name2, | |
44 | int flags)); | |
45 | static void DeleteSearches _ANSI_ARGS_((Var *arrayVarPtr)); | |
46 | static void DeleteArray _ANSI_ARGS_((Interp *iPtr, char *arrayName, | |
47 | Var *varPtr, int flags)); | |
48 | static Var * NewVar _ANSI_ARGS_((int space)); | |
49 | static ArraySearch * ParseSearchId _ANSI_ARGS_((Tcl_Interp *interp, | |
50 | Var *varPtr, char *varName, char *string)); | |
51 | static void VarErrMsg _ANSI_ARGS_((Tcl_Interp *interp, | |
52 | char *name1, char *name2, char *operation, | |
53 | char *reason)); | |
54 | \f | |
55 | /* | |
56 | *---------------------------------------------------------------------- | |
57 | * | |
58 | * Tcl_GetVar -- | |
59 | * | |
60 | * Return the value of a Tcl variable. | |
61 | * | |
62 | * Results: | |
63 | * The return value points to the current value of varName. If | |
64 | * the variable is not defined or can't be read because of a clash | |
65 | * in array usage then a NULL pointer is returned and an error | |
66 | * message is left in interp->result if the TCL_LEAVE_ERR_MSG | |
67 | * flag is set. Note: the return value is only valid up until | |
68 | * the next call to Tcl_SetVar or Tcl_SetVar2; if you depend on | |
69 | * the value lasting longer than that, then make yourself a private | |
70 | * copy. | |
71 | * | |
72 | * Side effects: | |
73 | * None. | |
74 | * | |
75 | *---------------------------------------------------------------------- | |
76 | */ | |
77 | ||
78 | char * | |
79 | Tcl_GetVar(interp, varName, flags) | |
80 | Tcl_Interp *interp; /* Command interpreter in which varName is | |
81 | * to be looked up. */ | |
82 | char *varName; /* Name of a variable in interp. */ | |
83 | int flags; /* OR-ed combination of TCL_GLOBAL_ONLY | |
84 | * or TCL_LEAVE_ERR_MSG bits. */ | |
85 | { | |
86 | register char *p; | |
87 | ||
88 | /* | |
89 | * If varName refers to an array (it ends with a parenthesized | |
90 | * element name), then handle it specially. | |
91 | */ | |
92 | ||
93 | for (p = varName; *p != '\0'; p++) { | |
94 | if (*p == '(') { | |
95 | char *result; | |
96 | char *open = p; | |
97 | ||
98 | do { | |
99 | p++; | |
100 | } while (*p != '\0'); | |
101 | p--; | |
102 | if (*p != ')') { | |
103 | goto scalar; | |
104 | } | |
105 | *open = '\0'; | |
106 | *p = '\0'; | |
107 | result = Tcl_GetVar2(interp, varName, open + 1, flags); | |
108 | *open = '('; | |
109 | *p = ')'; | |
110 | #ifdef sgi | |
111 | strcmp("a", "b"); /* XXX SGI compiler optimizer bug */ | |
112 | #endif | |
113 | return result; | |
114 | } | |
115 | } | |
116 | ||
117 | scalar: | |
118 | return Tcl_GetVar2(interp, varName, (char *) NULL, flags); | |
119 | } | |
120 | \f | |
121 | /* | |
122 | *---------------------------------------------------------------------- | |
123 | * | |
124 | * Tcl_GetVar2 -- | |
125 | * | |
126 | * Return the value of a Tcl variable, given a two-part name | |
127 | * consisting of array name and element within array. | |
128 | * | |
129 | * Results: | |
130 | * The return value points to the current value of the variable | |
131 | * given by name1 and name2. If the specified variable doesn't | |
132 | * exist, or if there is a clash in array usage, then NULL is | |
133 | * returned and a message will be left in interp->result if the | |
134 | * TCL_LEAVE_ERR_MSG flag is set. Note: the return value is | |
135 | * only valid up until the next call to Tcl_SetVar or Tcl_SetVar2; | |
136 | * if you depend on the value lasting longer than that, then make | |
137 | * yourself a private copy. | |
138 | * | |
139 | * Side effects: | |
140 | * None. | |
141 | * | |
142 | *---------------------------------------------------------------------- | |
143 | */ | |
144 | ||
145 | char * | |
146 | Tcl_GetVar2(interp, name1, name2, flags) | |
147 | Tcl_Interp *interp; /* Command interpreter in which variable is | |
148 | * to be looked up. */ | |
149 | char *name1; /* Name of array (if name2 is NULL) or | |
150 | * name of variable. */ | |
151 | char *name2; /* If non-null, gives name of element in | |
152 | * array. */ | |
153 | int flags; /* OR-ed combination of TCL_GLOBAL_ONLY | |
154 | * or TCL_LEAVE_ERR_MSG bits. */ | |
155 | { | |
156 | Tcl_HashEntry *hPtr; | |
157 | Var *varPtr; | |
158 | Interp *iPtr = (Interp *) interp; | |
159 | Var *arrayPtr = NULL; | |
160 | ||
161 | /* | |
162 | * Lookup the first name. | |
163 | */ | |
164 | ||
165 | if ((flags & TCL_GLOBAL_ONLY) || (iPtr->varFramePtr == NULL)) { | |
166 | hPtr = Tcl_FindHashEntry(&iPtr->globalTable, name1); | |
167 | } else { | |
168 | hPtr = Tcl_FindHashEntry(&iPtr->varFramePtr->varTable, name1); | |
169 | } | |
170 | if (hPtr == NULL) { | |
171 | if (flags & TCL_LEAVE_ERR_MSG) { | |
172 | VarErrMsg(interp, name1, name2, "read", noSuchVar); | |
173 | } | |
174 | return NULL; | |
175 | } | |
176 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
177 | if (varPtr->flags & VAR_UPVAR) { | |
178 | hPtr = varPtr->value.upvarPtr; | |
179 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
180 | } | |
181 | ||
182 | /* | |
183 | * If this is an array reference, then remember the traces on the array | |
184 | * and lookup the element within the array. | |
185 | */ | |
186 | ||
187 | if (name2 != NULL) { | |
188 | if (varPtr->flags & VAR_UNDEFINED) { | |
189 | if (flags & TCL_LEAVE_ERR_MSG) { | |
190 | VarErrMsg(interp, name1, name2, "read", noSuchVar); | |
191 | } | |
192 | return NULL; | |
193 | } else if (!(varPtr->flags & VAR_ARRAY)) { | |
194 | if (flags & TCL_LEAVE_ERR_MSG) { | |
195 | VarErrMsg(interp, name1, name2, "read", needArray); | |
196 | } | |
197 | return NULL; | |
198 | } | |
199 | arrayPtr = varPtr; | |
200 | hPtr = Tcl_FindHashEntry(varPtr->value.tablePtr, name2); | |
201 | if (hPtr == NULL) { | |
202 | if (flags & TCL_LEAVE_ERR_MSG) { | |
203 | VarErrMsg(interp, name1, name2, "read", noSuchElement); | |
204 | } | |
205 | return NULL; | |
206 | } | |
207 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
208 | } | |
209 | ||
210 | /* | |
211 | * Invoke any traces that have been set for the variable. | |
212 | */ | |
213 | ||
214 | if ((varPtr->tracePtr != NULL) | |
215 | || ((arrayPtr != NULL) && (arrayPtr->tracePtr != NULL))) { | |
216 | char *msg; | |
217 | ||
218 | msg = CallTraces(iPtr, arrayPtr, hPtr, name1, name2, | |
219 | (flags & TCL_GLOBAL_ONLY) | TCL_TRACE_READS); | |
220 | if (msg != NULL) { | |
221 | VarErrMsg(interp, name1, name2, "read", msg); | |
222 | return NULL; | |
223 | } | |
224 | ||
225 | /* | |
226 | * Watch out! The variable could have gotten re-allocated to | |
227 | * a larger size. Fortunately the hash table entry will still | |
228 | * be around. | |
229 | */ | |
230 | ||
231 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
232 | } | |
233 | if (varPtr->flags & (VAR_UNDEFINED|VAR_UPVAR|VAR_ARRAY)) { | |
234 | if (flags & TCL_LEAVE_ERR_MSG) { | |
235 | VarErrMsg(interp, name1, name2, "read", noSuchVar); | |
236 | } | |
237 | return NULL; | |
238 | } | |
239 | return varPtr->value.string; | |
240 | } | |
241 | \f | |
242 | /* | |
243 | *---------------------------------------------------------------------- | |
244 | * | |
245 | * Tcl_SetVar -- | |
246 | * | |
247 | * Change the value of a variable. | |
248 | * | |
249 | * Results: | |
250 | * Returns a pointer to the malloc'ed string holding the new | |
251 | * value of the variable. The caller should not modify this | |
252 | * string. If the write operation was disallowed then NULL | |
253 | * is returned; if the TCL_LEAVE_ERR_MSG flag is set, then | |
254 | * an explanatory message will be left in interp->result. | |
255 | * | |
256 | * Side effects: | |
257 | * If varName is defined as a local or global variable in interp, | |
258 | * its value is changed to newValue. If varName isn't currently | |
259 | * defined, then a new global variable by that name is created. | |
260 | * | |
261 | *---------------------------------------------------------------------- | |
262 | */ | |
263 | ||
264 | char * | |
265 | Tcl_SetVar(interp, varName, newValue, flags) | |
266 | Tcl_Interp *interp; /* Command interpreter in which varName is | |
267 | * to be looked up. */ | |
268 | char *varName; /* Name of a variable in interp. */ | |
269 | char *newValue; /* New value for varName. */ | |
270 | int flags; /* Various flags that tell how to set value: | |
271 | * any of TCL_GLOBAL_ONLY, TCL_APPEND_VALUE, | |
272 | * TCL_LIST_ELEMENT, TCL_NO_SPACE, or | |
273 | * TCL_LEAVE_ERR_MSG. */ | |
274 | { | |
275 | register char *p; | |
276 | ||
277 | /* | |
278 | * If varName refers to an array (it ends with a parenthesized | |
279 | * element name), then handle it specially. | |
280 | */ | |
281 | ||
282 | for (p = varName; *p != '\0'; p++) { | |
283 | if (*p == '(') { | |
284 | char *result; | |
285 | char *open = p; | |
286 | ||
287 | do { | |
288 | p++; | |
289 | } while (*p != '\0'); | |
290 | p--; | |
291 | if (*p != ')') { | |
292 | goto scalar; | |
293 | } | |
294 | *open = '\0'; | |
295 | *p = '\0'; | |
296 | result = Tcl_SetVar2(interp, varName, open+1, newValue, flags); | |
297 | *open = '('; | |
298 | *p = ')'; | |
299 | return result; | |
300 | } | |
301 | } | |
302 | ||
303 | scalar: | |
304 | return Tcl_SetVar2(interp, varName, (char *) NULL, newValue, flags); | |
305 | } | |
306 | \f | |
307 | /* | |
308 | *---------------------------------------------------------------------- | |
309 | * | |
310 | * Tcl_SetVar2 -- | |
311 | * | |
312 | * Given a two-part variable name, which may refer either to a | |
313 | * scalar variable or an element of an array, change the value | |
314 | * of the variable. If the named scalar or array or element | |
315 | * doesn't exist then create one. | |
316 | * | |
317 | * Results: | |
318 | * Returns a pointer to the malloc'ed string holding the new | |
319 | * value of the variable. The caller should not modify this | |
320 | * string. If the write operation was disallowed because an | |
321 | * array was expected but not found (or vice versa), then NULL | |
322 | * is returned; if the TCL_LEAVE_ERR_MSG flag is set, then | |
323 | * an explanatory message will be left in interp->result. | |
324 | * | |
325 | * Side effects: | |
326 | * The value of the given variable is set. If either the array | |
327 | * or the entry didn't exist then a new one is created. | |
328 | * | |
329 | *---------------------------------------------------------------------- | |
330 | */ | |
331 | ||
332 | char * | |
333 | Tcl_SetVar2(interp, name1, name2, newValue, flags) | |
334 | Tcl_Interp *interp; /* Command interpreter in which variable is | |
335 | * to be looked up. */ | |
336 | char *name1; /* If name2 is NULL, this is name of scalar | |
337 | * variable. Otherwise it is name of array. */ | |
338 | char *name2; /* Name of an element within array, or NULL. */ | |
339 | char *newValue; /* New value for variable. */ | |
340 | int flags; /* Various flags that tell how to set value: | |
341 | * any of TCL_GLOBAL_ONLY, TCL_APPEND_VALUE, | |
342 | * TCL_LIST_ELEMENT, and TCL_NO_SPACE, or | |
343 | * TCL_LEAVE_ERR_MSG . */ | |
344 | { | |
345 | Tcl_HashEntry *hPtr; | |
346 | register Var *varPtr = NULL; | |
347 | /* Initial value only used to stop compiler | |
348 | * from complaining; not really needed. */ | |
349 | register Interp *iPtr = (Interp *) interp; | |
350 | int length, new, listFlags; | |
351 | Var *arrayPtr = NULL; | |
352 | ||
353 | /* | |
354 | * Lookup the first name. | |
355 | */ | |
356 | ||
357 | if ((flags & TCL_GLOBAL_ONLY) || (iPtr->varFramePtr == NULL)) { | |
358 | hPtr = Tcl_CreateHashEntry(&iPtr->globalTable, name1, &new); | |
359 | } else { | |
360 | hPtr = Tcl_CreateHashEntry(&iPtr->varFramePtr->varTable, | |
361 | name1, &new); | |
362 | } | |
363 | if (!new) { | |
364 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
365 | if (varPtr->flags & VAR_UPVAR) { | |
366 | hPtr = varPtr->value.upvarPtr; | |
367 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
368 | } | |
369 | } | |
370 | ||
371 | /* | |
372 | * If this is an array reference, then create a new array (if | |
373 | * needed), remember any traces on the array, and lookup the | |
374 | * element within the array. | |
375 | */ | |
376 | ||
377 | if (name2 != NULL) { | |
378 | if (new) { | |
379 | varPtr = NewVar(0); | |
380 | Tcl_SetHashValue(hPtr, varPtr); | |
381 | varPtr->flags = VAR_ARRAY; | |
382 | varPtr->value.tablePtr = (Tcl_HashTable *) | |
383 | ckalloc(sizeof(Tcl_HashTable)); | |
384 | Tcl_InitHashTable(varPtr->value.tablePtr, TCL_STRING_KEYS); | |
385 | } else { | |
386 | if (varPtr->flags & VAR_UNDEFINED) { | |
387 | varPtr->flags = VAR_ARRAY; | |
388 | varPtr->value.tablePtr = (Tcl_HashTable *) | |
389 | ckalloc(sizeof(Tcl_HashTable)); | |
390 | Tcl_InitHashTable(varPtr->value.tablePtr, TCL_STRING_KEYS); | |
391 | } else if (!(varPtr->flags & VAR_ARRAY)) { | |
392 | if (flags & TCL_LEAVE_ERR_MSG) { | |
393 | VarErrMsg(interp, name1, name2, "set", needArray); | |
394 | } | |
395 | return NULL; | |
396 | } | |
397 | arrayPtr = varPtr; | |
398 | } | |
399 | hPtr = Tcl_CreateHashEntry(varPtr->value.tablePtr, name2, &new); | |
400 | } | |
401 | ||
402 | /* | |
403 | * Compute how many bytes will be needed for newValue (leave space | |
404 | * for a separating space between list elements). | |
405 | */ | |
406 | ||
407 | if (flags & TCL_LIST_ELEMENT) { | |
408 | length = Tcl_ScanElement(newValue, &listFlags) + 1; | |
409 | } else { | |
410 | length = strlen(newValue); | |
411 | } | |
412 | ||
413 | /* | |
414 | * If the variable doesn't exist then create a new one. If it | |
415 | * does exist then clear its current value unless this is an | |
416 | * append operation. | |
417 | */ | |
418 | ||
419 | if (new) { | |
420 | varPtr = NewVar(length); | |
421 | Tcl_SetHashValue(hPtr, varPtr); | |
422 | if ((arrayPtr != NULL) && (arrayPtr->searchPtr != NULL)) { | |
423 | DeleteSearches(arrayPtr); | |
424 | } | |
425 | } else { | |
426 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
427 | if (varPtr->flags & VAR_ARRAY) { | |
428 | if (flags & TCL_LEAVE_ERR_MSG) { | |
429 | VarErrMsg(interp, name1, name2, "set", isArray); | |
430 | } | |
431 | return NULL; | |
432 | } | |
433 | if (!(flags & TCL_APPEND_VALUE) || (varPtr->flags & VAR_UNDEFINED)) { | |
434 | varPtr->valueLength = 0; | |
435 | } | |
436 | } | |
437 | ||
438 | /* | |
439 | * Make sure there's enough space to hold the variable's | |
440 | * new value. If not, enlarge the variable's space. | |
441 | */ | |
442 | ||
443 | if ((length + varPtr->valueLength) >= varPtr->valueSpace) { | |
444 | Var *newVarPtr; | |
445 | int newSize; | |
446 | ||
447 | newSize = 2*varPtr->valueSpace; | |
448 | if (newSize <= (length + varPtr->valueLength)) { | |
449 | newSize += length; | |
450 | } | |
451 | newVarPtr = NewVar(newSize); | |
452 | newVarPtr->valueLength = varPtr->valueLength; | |
453 | newVarPtr->upvarUses = varPtr->upvarUses; | |
454 | newVarPtr->tracePtr = varPtr->tracePtr; | |
455 | strcpy(newVarPtr->value.string, varPtr->value.string); | |
456 | Tcl_SetHashValue(hPtr, newVarPtr); | |
457 | ckfree((char *) varPtr); | |
458 | varPtr = newVarPtr; | |
459 | } | |
460 | ||
461 | /* | |
462 | * Append the new value to the variable, either as a list | |
463 | * element or as a string. | |
464 | */ | |
465 | ||
466 | if (flags & TCL_LIST_ELEMENT) { | |
467 | if ((varPtr->valueLength > 0) && !(flags & TCL_NO_SPACE)) { | |
468 | varPtr->value.string[varPtr->valueLength] = ' '; | |
469 | varPtr->valueLength++; | |
470 | } | |
471 | varPtr->valueLength += Tcl_ConvertElement(newValue, | |
472 | varPtr->value.string + varPtr->valueLength, listFlags); | |
473 | varPtr->value.string[varPtr->valueLength] = 0; | |
474 | } else { | |
475 | strcpy(varPtr->value.string + varPtr->valueLength, newValue); | |
476 | varPtr->valueLength += length; | |
477 | } | |
478 | varPtr->flags &= ~VAR_UNDEFINED; | |
479 | ||
480 | /* | |
481 | * Invoke any write traces for the variable. | |
482 | */ | |
483 | ||
484 | if ((varPtr->tracePtr != NULL) | |
485 | || ((arrayPtr != NULL) && (arrayPtr->tracePtr != NULL))) { | |
486 | char *msg; | |
487 | ||
488 | msg = CallTraces(iPtr, arrayPtr, hPtr, name1, name2, | |
489 | (flags & TCL_GLOBAL_ONLY) | TCL_TRACE_WRITES); | |
490 | if (msg != NULL) { | |
491 | VarErrMsg(interp, name1, name2, "set", msg); | |
492 | return NULL; | |
493 | } | |
494 | ||
495 | /* | |
496 | * Watch out! The variable could have gotten re-allocated to | |
497 | * a larger size. Fortunately the hash table entry will still | |
498 | * be around. | |
499 | */ | |
500 | ||
501 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
502 | } | |
503 | return varPtr->value.string; | |
504 | } | |
505 | \f | |
506 | /* | |
507 | *---------------------------------------------------------------------- | |
508 | * | |
509 | * Tcl_UnsetVar -- | |
510 | * | |
511 | * Delete a variable, so that it may not be accessed anymore. | |
512 | * | |
513 | * Results: | |
514 | * Returns 0 if the variable was successfully deleted, -1 | |
515 | * if the variable can't be unset. In the event of an error, | |
516 | * if the TCL_LEAVE_ERR_MSG flag is set then an error message | |
517 | * is left in interp->result. | |
518 | * | |
519 | * Side effects: | |
520 | * If varName is defined as a local or global variable in interp, | |
521 | * it is deleted. | |
522 | * | |
523 | *---------------------------------------------------------------------- | |
524 | */ | |
525 | ||
526 | int | |
527 | Tcl_UnsetVar(interp, varName, flags) | |
528 | Tcl_Interp *interp; /* Command interpreter in which varName is | |
529 | * to be looked up. */ | |
530 | char *varName; /* Name of a variable in interp. May be | |
531 | * either a scalar name or an array name | |
532 | * or an element in an array. */ | |
533 | int flags; /* OR-ed combination of any of | |
534 | * TCL_GLOBAL_ONLY or TCL_LEAVE_ERR_MSG. */ | |
535 | { | |
536 | register char *p; | |
537 | int result; | |
538 | ||
539 | /* | |
540 | * Figure out whether this is an array reference, then call | |
541 | * Tcl_UnsetVar2 to do all the real work. | |
542 | */ | |
543 | ||
544 | for (p = varName; *p != '\0'; p++) { | |
545 | if (*p == '(') { | |
546 | char *open = p; | |
547 | ||
548 | do { | |
549 | p++; | |
550 | } while (*p != '\0'); | |
551 | p--; | |
552 | if (*p != ')') { | |
553 | goto scalar; | |
554 | } | |
555 | *open = '\0'; | |
556 | *p = '\0'; | |
557 | result = Tcl_UnsetVar2(interp, varName, open+1, flags); | |
558 | *open = '('; | |
559 | *p = ')'; | |
560 | return result; | |
561 | } | |
562 | } | |
563 | ||
564 | scalar: | |
565 | return Tcl_UnsetVar2(interp, varName, (char *) NULL, flags); | |
566 | } | |
567 | \f | |
568 | /* | |
569 | *---------------------------------------------------------------------- | |
570 | * | |
571 | * Tcl_UnsetVar2 -- | |
572 | * | |
573 | * Delete a variable, given a 2-part name. | |
574 | * | |
575 | * Results: | |
576 | * Returns 0 if the variable was successfully deleted, -1 | |
577 | * if the variable can't be unset. In the event of an error, | |
578 | * if the TCL_LEAVE_ERR_MSG flag is set then an error message | |
579 | * is left in interp->result. | |
580 | * | |
581 | * Side effects: | |
582 | * If name1 and name2 indicate a local or global variable in interp, | |
583 | * it is deleted. If name1 is an array name and name2 is NULL, then | |
584 | * the whole array is deleted. | |
585 | * | |
586 | *---------------------------------------------------------------------- | |
587 | */ | |
588 | ||
589 | int | |
590 | Tcl_UnsetVar2(interp, name1, name2, flags) | |
591 | Tcl_Interp *interp; /* Command interpreter in which varName is | |
592 | * to be looked up. */ | |
593 | char *name1; /* Name of variable or array. */ | |
594 | char *name2; /* Name of element within array or NULL. */ | |
595 | int flags; /* OR-ed combination of any of | |
596 | * TCL_GLOBAL_ONLY or TCL_LEAVE_ERR_MSG. */ | |
597 | { | |
598 | Tcl_HashEntry *hPtr, dummyEntry; | |
599 | Var *varPtr, dummyVar; | |
600 | Interp *iPtr = (Interp *) interp; | |
601 | Var *arrayPtr = NULL; | |
602 | ||
603 | if ((flags & TCL_GLOBAL_ONLY) || (iPtr->varFramePtr == NULL)) { | |
604 | hPtr = Tcl_FindHashEntry(&iPtr->globalTable, name1); | |
605 | } else { | |
606 | hPtr = Tcl_FindHashEntry(&iPtr->varFramePtr->varTable, name1); | |
607 | } | |
608 | if (hPtr == NULL) { | |
609 | if (flags & TCL_LEAVE_ERR_MSG) { | |
610 | VarErrMsg(interp, name1, name2, "unset", noSuchVar); | |
611 | } | |
612 | return -1; | |
613 | } | |
614 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
615 | ||
616 | /* | |
617 | * For global variables referenced in procedures, leave the procedure's | |
618 | * reference variable in place, but unset the global variable. Can't | |
619 | * decrement the actual variable's use count, since we didn't delete | |
620 | * the reference variable. | |
621 | */ | |
622 | ||
623 | if (varPtr->flags & VAR_UPVAR) { | |
624 | hPtr = varPtr->value.upvarPtr; | |
625 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
626 | } | |
627 | ||
628 | /* | |
629 | * If the variable being deleted is an element of an array, then | |
630 | * remember trace procedures on the overall array and find the | |
631 | * element to delete. | |
632 | */ | |
633 | ||
634 | if (name2 != NULL) { | |
635 | if (!(varPtr->flags & VAR_ARRAY)) { | |
636 | if (flags & TCL_LEAVE_ERR_MSG) { | |
637 | VarErrMsg(interp, name1, name2, "unset", needArray); | |
638 | } | |
639 | return -1; | |
640 | } | |
641 | if (varPtr->searchPtr != NULL) { | |
642 | DeleteSearches(varPtr); | |
643 | } | |
644 | arrayPtr = varPtr; | |
645 | hPtr = Tcl_FindHashEntry(varPtr->value.tablePtr, name2); | |
646 | if (hPtr == NULL) { | |
647 | if (flags & TCL_LEAVE_ERR_MSG) { | |
648 | VarErrMsg(interp, name1, name2, "unset", noSuchElement); | |
649 | } | |
650 | return -1; | |
651 | } | |
652 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
653 | } | |
654 | ||
655 | /* | |
656 | * If there is a trace active on this variable or if the variable | |
657 | * is already being deleted then don't delete the variable: it | |
658 | * isn't safe, since there are procedures higher up on the stack | |
659 | * that will use pointers to the variable. Also don't delete an | |
660 | * array if there are traces active on any of its elements. | |
661 | */ | |
662 | ||
663 | if (varPtr->flags & | |
664 | (VAR_TRACE_ACTIVE|VAR_ELEMENT_ACTIVE)) { | |
665 | if (flags & TCL_LEAVE_ERR_MSG) { | |
666 | VarErrMsg(interp, name1, name2, "unset", traceActive); | |
667 | } | |
668 | return -1; | |
669 | } | |
670 | ||
671 | /* | |
672 | * The code below is tricky, because of the possibility that | |
673 | * a trace procedure might try to access a variable being | |
674 | * deleted. To handle this situation gracefully, copy the | |
675 | * contents of the variable and its hash table entry to | |
676 | * dummy variables, then clean up the actual variable so that | |
677 | * it's been completely deleted before the traces are called. | |
678 | * Then call the traces, and finally clean up the variable's | |
679 | * storage using the dummy copies. | |
680 | */ | |
681 | ||
682 | dummyVar = *varPtr; | |
683 | Tcl_SetHashValue(&dummyEntry, &dummyVar); | |
684 | if (varPtr->upvarUses == 0) { | |
685 | Tcl_DeleteHashEntry(hPtr); | |
686 | ckfree((char *) varPtr); | |
687 | } else { | |
688 | varPtr->flags = VAR_UNDEFINED; | |
689 | varPtr->tracePtr = NULL; | |
690 | } | |
691 | ||
692 | /* | |
693 | * Call trace procedures for the variable being deleted and delete | |
694 | * its traces. | |
695 | */ | |
696 | ||
697 | if ((dummyVar.tracePtr != NULL) | |
698 | || ((arrayPtr != NULL) && (arrayPtr->tracePtr != NULL))) { | |
699 | (void) CallTraces(iPtr, arrayPtr, &dummyEntry, name1, name2, | |
700 | (flags & TCL_GLOBAL_ONLY) | TCL_TRACE_UNSETS); | |
701 | while (dummyVar.tracePtr != NULL) { | |
702 | VarTrace *tracePtr = dummyVar.tracePtr; | |
703 | dummyVar.tracePtr = tracePtr->nextPtr; | |
704 | ckfree((char *) tracePtr); | |
705 | } | |
706 | } | |
707 | ||
708 | /* | |
709 | * If the variable is an array, delete all of its elements. This | |
710 | * must be done after calling the traces on the array, above (that's | |
711 | * the way traces are defined). | |
712 | */ | |
713 | ||
714 | if (dummyVar.flags & VAR_ARRAY) { | |
715 | DeleteArray(iPtr, name1, &dummyVar, | |
716 | (flags & TCL_GLOBAL_ONLY) | TCL_TRACE_UNSETS); | |
717 | } | |
718 | if (dummyVar.flags & VAR_UNDEFINED) { | |
719 | if (flags & TCL_LEAVE_ERR_MSG) { | |
720 | VarErrMsg(interp, name1, name2, "unset", | |
721 | (name2 == NULL) ? noSuchVar : noSuchElement); | |
722 | } | |
723 | return -1; | |
724 | } | |
725 | return 0; | |
726 | } | |
727 | \f | |
728 | /* | |
729 | *---------------------------------------------------------------------- | |
730 | * | |
731 | * Tcl_TraceVar -- | |
732 | * | |
733 | * Arrange for reads and/or writes to a variable to cause a | |
734 | * procedure to be invoked, which can monitor the operations | |
735 | * and/or change their actions. | |
736 | * | |
737 | * Results: | |
738 | * A standard Tcl return value. | |
739 | * | |
740 | * Side effects: | |
741 | * A trace is set up on the variable given by varName, such that | |
742 | * future references to the variable will be intermediated by | |
743 | * proc. See the manual entry for complete details on the calling | |
744 | * sequence for proc. | |
745 | * | |
746 | *---------------------------------------------------------------------- | |
747 | */ | |
748 | ||
749 | int | |
750 | Tcl_TraceVar(interp, varName, flags, proc, clientData) | |
751 | Tcl_Interp *interp; /* Interpreter in which variable is | |
752 | * to be traced. */ | |
753 | char *varName; /* Name of variable; may end with "(index)" | |
754 | * to signify an array reference. */ | |
755 | int flags; /* OR-ed collection of bits, including any | |
756 | * of TCL_TRACE_READS, TCL_TRACE_WRITES, | |
757 | * TCL_TRACE_UNSETS, and TCL_GLOBAL_ONLY. */ | |
758 | Tcl_VarTraceProc *proc; /* Procedure to call when specified ops are | |
759 | * invoked upon varName. */ | |
760 | ClientData clientData; /* Arbitrary argument to pass to proc. */ | |
761 | { | |
762 | register char *p; | |
763 | ||
764 | /* | |
765 | * If varName refers to an array (it ends with a parenthesized | |
766 | * element name), then handle it specially. | |
767 | */ | |
768 | ||
769 | for (p = varName; *p != '\0'; p++) { | |
770 | if (*p == '(') { | |
771 | int result; | |
772 | char *open = p; | |
773 | ||
774 | do { | |
775 | p++; | |
776 | } while (*p != '\0'); | |
777 | p--; | |
778 | if (*p != ')') { | |
779 | goto scalar; | |
780 | } | |
781 | *open = '\0'; | |
782 | *p = '\0'; | |
783 | result = Tcl_TraceVar2(interp, varName, open+1, flags, | |
784 | proc, clientData); | |
785 | *open = '('; | |
786 | *p = ')'; | |
787 | return result; | |
788 | } | |
789 | } | |
790 | ||
791 | scalar: | |
792 | return Tcl_TraceVar2(interp, varName, (char *) NULL, flags, | |
793 | proc, clientData); | |
794 | } | |
795 | \f | |
796 | /* | |
797 | *---------------------------------------------------------------------- | |
798 | * | |
799 | * Tcl_TraceVar2 -- | |
800 | * | |
801 | * Arrange for reads and/or writes to a variable to cause a | |
802 | * procedure to be invoked, which can monitor the operations | |
803 | * and/or change their actions. | |
804 | * | |
805 | * Results: | |
806 | * A standard Tcl return value. | |
807 | * | |
808 | * Side effects: | |
809 | * A trace is set up on the variable given by name1 and name2, such | |
810 | * that future references to the variable will be intermediated by | |
811 | * proc. See the manual entry for complete details on the calling | |
812 | * sequence for proc. | |
813 | * | |
814 | *---------------------------------------------------------------------- | |
815 | */ | |
816 | ||
817 | int | |
818 | Tcl_TraceVar2(interp, name1, name2, flags, proc, clientData) | |
819 | Tcl_Interp *interp; /* Interpreter in which variable is | |
820 | * to be traced. */ | |
821 | char *name1; /* Name of scalar variable or array. */ | |
822 | char *name2; /* Name of element within array; NULL means | |
823 | * trace applies to scalar variable or array | |
824 | * as-a-whole. */ | |
825 | int flags; /* OR-ed collection of bits, including any | |
826 | * of TCL_TRACE_READS, TCL_TRACE_WRITES, | |
827 | * TCL_TRACE_UNSETS, and TCL_GLOBAL_ONLY. */ | |
828 | Tcl_VarTraceProc *proc; /* Procedure to call when specified ops are | |
829 | * invoked upon varName. */ | |
830 | ClientData clientData; /* Arbitrary argument to pass to proc. */ | |
831 | { | |
832 | Tcl_HashEntry *hPtr; | |
833 | Var *varPtr = NULL; /* Initial value only used to stop compiler | |
834 | * from complaining; not really needed. */ | |
835 | Interp *iPtr = (Interp *) interp; | |
836 | register VarTrace *tracePtr; | |
837 | int new; | |
838 | ||
839 | /* | |
840 | * Locate the variable, making a new (undefined) one if necessary. | |
841 | */ | |
842 | ||
843 | if ((flags & TCL_GLOBAL_ONLY) || (iPtr->varFramePtr == NULL)) { | |
844 | hPtr = Tcl_CreateHashEntry(&iPtr->globalTable, name1, &new); | |
845 | } else { | |
846 | hPtr = Tcl_CreateHashEntry(&iPtr->varFramePtr->varTable, name1, &new); | |
847 | } | |
848 | if (!new) { | |
849 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
850 | if (varPtr->flags & VAR_UPVAR) { | |
851 | hPtr = varPtr->value.upvarPtr; | |
852 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
853 | } | |
854 | } | |
855 | ||
856 | /* | |
857 | * If the trace is to be on an array element, make sure that the | |
858 | * variable is an array variable. If the variable doesn't exist | |
859 | * then define it as an empty array. Then find the specific | |
860 | * array element. | |
861 | */ | |
862 | ||
863 | if (name2 != NULL) { | |
864 | if (new) { | |
865 | varPtr = NewVar(0); | |
866 | Tcl_SetHashValue(hPtr, varPtr); | |
867 | varPtr->flags = VAR_ARRAY; | |
868 | varPtr->value.tablePtr = (Tcl_HashTable *) | |
869 | ckalloc(sizeof(Tcl_HashTable)); | |
870 | Tcl_InitHashTable(varPtr->value.tablePtr, TCL_STRING_KEYS); | |
871 | } else { | |
872 | if (varPtr->flags & VAR_UNDEFINED) { | |
873 | varPtr->flags = VAR_ARRAY; | |
874 | varPtr->value.tablePtr = (Tcl_HashTable *) | |
875 | ckalloc(sizeof(Tcl_HashTable)); | |
876 | Tcl_InitHashTable(varPtr->value.tablePtr, TCL_STRING_KEYS); | |
877 | } else if (!(varPtr->flags & VAR_ARRAY)) { | |
878 | iPtr->result = needArray; | |
879 | return TCL_ERROR; | |
880 | } | |
881 | } | |
882 | hPtr = Tcl_CreateHashEntry(varPtr->value.tablePtr, name2, &new); | |
883 | } | |
884 | ||
885 | if (new) { | |
886 | if ((name2 != NULL) && (varPtr->searchPtr != NULL)) { | |
887 | DeleteSearches(varPtr); | |
888 | } | |
889 | varPtr = NewVar(0); | |
890 | varPtr->flags = VAR_UNDEFINED; | |
891 | Tcl_SetHashValue(hPtr, varPtr); | |
892 | } else { | |
893 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
894 | } | |
895 | ||
896 | /* | |
897 | * Set up trace information. | |
898 | */ | |
899 | ||
900 | tracePtr = (VarTrace *) ckalloc(sizeof(VarTrace)); | |
901 | tracePtr->traceProc = proc; | |
902 | tracePtr->clientData = clientData; | |
903 | tracePtr->flags = flags & | |
904 | (TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS); | |
905 | tracePtr->nextPtr = varPtr->tracePtr; | |
906 | varPtr->tracePtr = tracePtr; | |
907 | return TCL_OK; | |
908 | } | |
909 | \f | |
910 | /* | |
911 | *---------------------------------------------------------------------- | |
912 | * | |
913 | * Tcl_UntraceVar -- | |
914 | * | |
915 | * Remove a previously-created trace for a variable. | |
916 | * | |
917 | * Results: | |
918 | * None. | |
919 | * | |
920 | * Side effects: | |
921 | * If there exists a trace for the variable given by varName | |
922 | * with the given flags, proc, and clientData, then that trace | |
923 | * is removed. | |
924 | * | |
925 | *---------------------------------------------------------------------- | |
926 | */ | |
927 | ||
928 | void | |
929 | Tcl_UntraceVar(interp, varName, flags, proc, clientData) | |
930 | Tcl_Interp *interp; /* Interpreter containing traced variable. */ | |
931 | char *varName; /* Name of variable; may end with "(index)" | |
932 | * to signify an array reference. */ | |
933 | int flags; /* OR-ed collection of bits describing | |
934 | * current trace, including any of | |
935 | * TCL_TRACE_READS, TCL_TRACE_WRITES, | |
936 | * TCL_TRACE_UNSETS, and TCL_GLOBAL_ONLY. */ | |
937 | Tcl_VarTraceProc *proc; /* Procedure assocated with trace. */ | |
938 | ClientData clientData; /* Arbitrary argument to pass to proc. */ | |
939 | { | |
940 | register char *p; | |
941 | ||
942 | /* | |
943 | * If varName refers to an array (it ends with a parenthesized | |
944 | * element name), then handle it specially. | |
945 | */ | |
946 | ||
947 | for (p = varName; *p != '\0'; p++) { | |
948 | if (*p == '(') { | |
949 | char *open = p; | |
950 | ||
951 | do { | |
952 | p++; | |
953 | } while (*p != '\0'); | |
954 | p--; | |
955 | if (*p != ')') { | |
956 | goto scalar; | |
957 | } | |
958 | *open = '\0'; | |
959 | *p = '\0'; | |
960 | Tcl_UntraceVar2(interp, varName, open+1, flags, proc, clientData); | |
961 | *open = '('; | |
962 | *p = ')'; | |
963 | return; | |
964 | } | |
965 | } | |
966 | ||
967 | scalar: | |
968 | Tcl_UntraceVar2(interp, varName, (char *) NULL, flags, proc, clientData); | |
969 | } | |
970 | \f | |
971 | /* | |
972 | *---------------------------------------------------------------------- | |
973 | * | |
974 | * Tcl_UntraceVar2 -- | |
975 | * | |
976 | * Remove a previously-created trace for a variable. | |
977 | * | |
978 | * Results: | |
979 | * None. | |
980 | * | |
981 | * Side effects: | |
982 | * If there exists a trace for the variable given by name1 | |
983 | * and name2 with the given flags, proc, and clientData, then | |
984 | * that trace is removed. | |
985 | * | |
986 | *---------------------------------------------------------------------- | |
987 | */ | |
988 | ||
989 | void | |
990 | Tcl_UntraceVar2(interp, name1, name2, flags, proc, clientData) | |
991 | Tcl_Interp *interp; /* Interpreter containing traced variable. */ | |
992 | char *name1; /* Name of variable or array. */ | |
993 | char *name2; /* Name of element within array; NULL means | |
994 | * trace applies to scalar variable or array | |
995 | * as-a-whole. */ | |
996 | int flags; /* OR-ed collection of bits describing | |
997 | * current trace, including any of | |
998 | * TCL_TRACE_READS, TCL_TRACE_WRITES, | |
999 | * TCL_TRACE_UNSETS, and TCL_GLOBAL_ONLY. */ | |
1000 | Tcl_VarTraceProc *proc; /* Procedure assocated with trace. */ | |
1001 | ClientData clientData; /* Arbitrary argument to pass to proc. */ | |
1002 | { | |
1003 | register VarTrace *tracePtr; | |
1004 | VarTrace *prevPtr; | |
1005 | Var *varPtr; | |
1006 | Interp *iPtr = (Interp *) interp; | |
1007 | Tcl_HashEntry *hPtr; | |
1008 | ActiveVarTrace *activePtr; | |
1009 | ||
1010 | /* | |
1011 | * First, lookup the variable. | |
1012 | */ | |
1013 | ||
1014 | if ((flags & TCL_GLOBAL_ONLY) || (iPtr->varFramePtr == NULL)) { | |
1015 | hPtr = Tcl_FindHashEntry(&iPtr->globalTable, name1); | |
1016 | } else { | |
1017 | hPtr = Tcl_FindHashEntry(&iPtr->varFramePtr->varTable, name1); | |
1018 | } | |
1019 | if (hPtr == NULL) { | |
1020 | return; | |
1021 | } | |
1022 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1023 | if (varPtr->flags & VAR_UPVAR) { | |
1024 | hPtr = varPtr->value.upvarPtr; | |
1025 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1026 | } | |
1027 | if (name2 != NULL) { | |
1028 | if (!(varPtr->flags & VAR_ARRAY)) { | |
1029 | return; | |
1030 | } | |
1031 | hPtr = Tcl_FindHashEntry(varPtr->value.tablePtr, name2); | |
1032 | if (hPtr == NULL) { | |
1033 | return; | |
1034 | } | |
1035 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1036 | } | |
1037 | ||
1038 | flags &= (TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS); | |
1039 | for (tracePtr = varPtr->tracePtr, prevPtr = NULL; ; | |
1040 | prevPtr = tracePtr, tracePtr = tracePtr->nextPtr) { | |
1041 | if (tracePtr == NULL) { | |
1042 | return; | |
1043 | } | |
1044 | if ((tracePtr->traceProc == proc) && (tracePtr->flags == flags) | |
1045 | && (tracePtr->clientData == clientData)) { | |
1046 | break; | |
1047 | } | |
1048 | } | |
1049 | ||
1050 | /* | |
1051 | * The code below makes it possible to delete traces while traces | |
1052 | * are active: it makes sure that the deleted trace won't be | |
1053 | * processed by CallTraces. | |
1054 | */ | |
1055 | ||
1056 | for (activePtr = iPtr->activeTracePtr; activePtr != NULL; | |
1057 | activePtr = activePtr->nextPtr) { | |
1058 | if (activePtr->nextTracePtr == tracePtr) { | |
1059 | activePtr->nextTracePtr = tracePtr->nextPtr; | |
1060 | } | |
1061 | } | |
1062 | if (prevPtr == NULL) { | |
1063 | varPtr->tracePtr = tracePtr->nextPtr; | |
1064 | } else { | |
1065 | prevPtr->nextPtr = tracePtr->nextPtr; | |
1066 | } | |
1067 | ckfree((char *) tracePtr); | |
1068 | } | |
1069 | \f | |
1070 | /* | |
1071 | *---------------------------------------------------------------------- | |
1072 | * | |
1073 | * Tcl_VarTraceInfo -- | |
1074 | * | |
1075 | * Return the clientData value associated with a trace on a | |
1076 | * variable. This procedure can also be used to step through | |
1077 | * all of the traces on a particular variable that have the | |
1078 | * same trace procedure. | |
1079 | * | |
1080 | * Results: | |
1081 | * The return value is the clientData value associated with | |
1082 | * a trace on the given variable. Information will only be | |
1083 | * returned for a trace with proc as trace procedure. If | |
1084 | * the clientData argument is NULL then the first such trace is | |
1085 | * returned; otherwise, the next relevant one after the one | |
1086 | * given by clientData will be returned. If the variable | |
1087 | * doesn't exist, or if there are no (more) traces for it, | |
1088 | * then NULL is returned. | |
1089 | * | |
1090 | * Side effects: | |
1091 | * None. | |
1092 | * | |
1093 | *---------------------------------------------------------------------- | |
1094 | */ | |
1095 | ||
1096 | ClientData | |
1097 | Tcl_VarTraceInfo(interp, varName, flags, proc, prevClientData) | |
1098 | Tcl_Interp *interp; /* Interpreter containing variable. */ | |
1099 | char *varName; /* Name of variable; may end with "(index)" | |
1100 | * to signify an array reference. */ | |
1101 | int flags; /* 0 or TCL_GLOBAL_ONLY. */ | |
1102 | Tcl_VarTraceProc *proc; /* Procedure assocated with trace. */ | |
1103 | ClientData prevClientData; /* If non-NULL, gives last value returned | |
1104 | * by this procedure, so this call will | |
1105 | * return the next trace after that one. | |
1106 | * If NULL, this call will return the | |
1107 | * first trace. */ | |
1108 | { | |
1109 | register char *p; | |
1110 | ||
1111 | /* | |
1112 | * If varName refers to an array (it ends with a parenthesized | |
1113 | * element name), then handle it specially. | |
1114 | */ | |
1115 | ||
1116 | for (p = varName; *p != '\0'; p++) { | |
1117 | if (*p == '(') { | |
1118 | ClientData result; | |
1119 | char *open = p; | |
1120 | ||
1121 | do { | |
1122 | p++; | |
1123 | } while (*p != '\0'); | |
1124 | p--; | |
1125 | if (*p != ')') { | |
1126 | goto scalar; | |
1127 | } | |
1128 | *open = '\0'; | |
1129 | *p = '\0'; | |
1130 | result = Tcl_VarTraceInfo2(interp, varName, open+1, flags, proc, | |
1131 | prevClientData); | |
1132 | *open = '('; | |
1133 | *p = ')'; | |
1134 | return result; | |
1135 | } | |
1136 | } | |
1137 | ||
1138 | scalar: | |
1139 | return Tcl_VarTraceInfo2(interp, varName, (char *) NULL, flags, proc, | |
1140 | prevClientData); | |
1141 | } | |
1142 | \f | |
1143 | /* | |
1144 | *---------------------------------------------------------------------- | |
1145 | * | |
1146 | * Tcl_VarTraceInfo2 -- | |
1147 | * | |
1148 | * Same as Tcl_VarTraceInfo, except takes name in two pieces | |
1149 | * instead of one. | |
1150 | * | |
1151 | * Results: | |
1152 | * Same as Tcl_VarTraceInfo. | |
1153 | * | |
1154 | * Side effects: | |
1155 | * None. | |
1156 | * | |
1157 | *---------------------------------------------------------------------- | |
1158 | */ | |
1159 | ||
1160 | ClientData | |
1161 | Tcl_VarTraceInfo2(interp, name1, name2, flags, proc, prevClientData) | |
1162 | Tcl_Interp *interp; /* Interpreter containing variable. */ | |
1163 | char *name1; /* Name of variable or array. */ | |
1164 | char *name2; /* Name of element within array; NULL means | |
1165 | * trace applies to scalar variable or array | |
1166 | * as-a-whole. */ | |
1167 | int flags; /* 0 or TCL_GLOBAL_ONLY. */ | |
1168 | Tcl_VarTraceProc *proc; /* Procedure assocated with trace. */ | |
1169 | ClientData prevClientData; /* If non-NULL, gives last value returned | |
1170 | * by this procedure, so this call will | |
1171 | * return the next trace after that one. | |
1172 | * If NULL, this call will return the | |
1173 | * first trace. */ | |
1174 | { | |
1175 | register VarTrace *tracePtr; | |
1176 | Var *varPtr; | |
1177 | Interp *iPtr = (Interp *) interp; | |
1178 | Tcl_HashEntry *hPtr; | |
1179 | ||
1180 | /* | |
1181 | * First, lookup the variable. | |
1182 | */ | |
1183 | ||
1184 | if ((flags & TCL_GLOBAL_ONLY) || (iPtr->varFramePtr == NULL)) { | |
1185 | hPtr = Tcl_FindHashEntry(&iPtr->globalTable, name1); | |
1186 | } else { | |
1187 | hPtr = Tcl_FindHashEntry(&iPtr->varFramePtr->varTable, name1); | |
1188 | } | |
1189 | if (hPtr == NULL) { | |
1190 | return NULL; | |
1191 | } | |
1192 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1193 | if (varPtr->flags & VAR_UPVAR) { | |
1194 | hPtr = varPtr->value.upvarPtr; | |
1195 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1196 | } | |
1197 | if (name2 != NULL) { | |
1198 | if (!(varPtr->flags & VAR_ARRAY)) { | |
1199 | return NULL; | |
1200 | } | |
1201 | hPtr = Tcl_FindHashEntry(varPtr->value.tablePtr, name2); | |
1202 | if (hPtr == NULL) { | |
1203 | return NULL; | |
1204 | } | |
1205 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1206 | } | |
1207 | ||
1208 | /* | |
1209 | * Find the relevant trace, if any, and return its clientData. | |
1210 | */ | |
1211 | ||
1212 | tracePtr = varPtr->tracePtr; | |
1213 | if (prevClientData != NULL) { | |
1214 | for ( ; tracePtr != NULL; tracePtr = tracePtr->nextPtr) { | |
1215 | if ((tracePtr->clientData == prevClientData) | |
1216 | && (tracePtr->traceProc == proc)) { | |
1217 | tracePtr = tracePtr->nextPtr; | |
1218 | break; | |
1219 | } | |
1220 | } | |
1221 | } | |
1222 | for ( ; tracePtr != NULL; tracePtr = tracePtr->nextPtr) { | |
1223 | if (tracePtr->traceProc == proc) { | |
1224 | return tracePtr->clientData; | |
1225 | } | |
1226 | } | |
1227 | return NULL; | |
1228 | } | |
1229 | \f | |
1230 | /* | |
1231 | *---------------------------------------------------------------------- | |
1232 | * | |
1233 | * Tcl_SetCmd -- | |
1234 | * | |
1235 | * This procedure is invoked to process the "set" Tcl command. | |
1236 | * See the user documentation for details on what it does. | |
1237 | * | |
1238 | * Results: | |
1239 | * A standard Tcl result value. | |
1240 | * | |
1241 | * Side effects: | |
1242 | * A variable's value may be changed. | |
1243 | * | |
1244 | *---------------------------------------------------------------------- | |
1245 | */ | |
1246 | ||
1247 | /* ARGSUSED */ | |
1248 | int | |
1249 | Tcl_SetCmd(dummy, interp, argc, argv) | |
1250 | ClientData dummy; /* Not used. */ | |
1251 | register Tcl_Interp *interp; /* Current interpreter. */ | |
1252 | int argc; /* Number of arguments. */ | |
1253 | char **argv; /* Argument strings. */ | |
1254 | { | |
1255 | if (argc == 2) { | |
1256 | char *value; | |
1257 | ||
1258 | value = Tcl_GetVar(interp, argv[1], TCL_LEAVE_ERR_MSG); | |
1259 | if (value == NULL) { | |
1260 | return TCL_ERROR; | |
1261 | } | |
1262 | interp->result = value; | |
1263 | return TCL_OK; | |
1264 | } else if (argc == 3) { | |
1265 | char *result; | |
1266 | ||
1267 | result = Tcl_SetVar(interp, argv[1], argv[2], TCL_LEAVE_ERR_MSG); | |
1268 | if (result == NULL) { | |
1269 | return TCL_ERROR; | |
1270 | } | |
1271 | interp->result = result; | |
1272 | return TCL_OK; | |
1273 | } else { | |
1274 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1275 | argv[0], " varName ?newValue?\"", (char *) NULL); | |
1276 | return TCL_ERROR; | |
1277 | } | |
1278 | } | |
1279 | \f | |
1280 | /* | |
1281 | *---------------------------------------------------------------------- | |
1282 | * | |
1283 | * Tcl_UnsetCmd -- | |
1284 | * | |
1285 | * This procedure is invoked to process the "unset" Tcl command. | |
1286 | * See the user documentation for details on what it does. | |
1287 | * | |
1288 | * Results: | |
1289 | * A standard Tcl result value. | |
1290 | * | |
1291 | * Side effects: | |
1292 | * See the user documentation. | |
1293 | * | |
1294 | *---------------------------------------------------------------------- | |
1295 | */ | |
1296 | ||
1297 | /* ARGSUSED */ | |
1298 | int | |
1299 | Tcl_UnsetCmd(dummy, interp, argc, argv) | |
1300 | ClientData dummy; /* Not used. */ | |
1301 | register Tcl_Interp *interp; /* Current interpreter. */ | |
1302 | int argc; /* Number of arguments. */ | |
1303 | char **argv; /* Argument strings. */ | |
1304 | { | |
1305 | int i; | |
1306 | ||
1307 | if (argc < 2) { | |
1308 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1309 | argv[0], " varName ?varName ...?\"", (char *) NULL); | |
1310 | return TCL_ERROR; | |
1311 | } | |
1312 | for (i = 1; i < argc; i++) { | |
1313 | if (Tcl_UnsetVar(interp, argv[i], TCL_LEAVE_ERR_MSG) != 0) { | |
1314 | return TCL_ERROR; | |
1315 | } | |
1316 | } | |
1317 | return TCL_OK; | |
1318 | } | |
1319 | \f | |
1320 | /* | |
1321 | *---------------------------------------------------------------------- | |
1322 | * | |
1323 | * Tcl_AppendCmd -- | |
1324 | * | |
1325 | * This procedure is invoked to process the "append" Tcl command. | |
1326 | * See the user documentation for details on what it does. | |
1327 | * | |
1328 | * Results: | |
1329 | * A standard Tcl result value. | |
1330 | * | |
1331 | * Side effects: | |
1332 | * A variable's value may be changed. | |
1333 | * | |
1334 | *---------------------------------------------------------------------- | |
1335 | */ | |
1336 | ||
1337 | /* ARGSUSED */ | |
1338 | int | |
1339 | Tcl_AppendCmd(dummy, interp, argc, argv) | |
1340 | ClientData dummy; /* Not used. */ | |
1341 | register Tcl_Interp *interp; /* Current interpreter. */ | |
1342 | int argc; /* Number of arguments. */ | |
1343 | char **argv; /* Argument strings. */ | |
1344 | { | |
1345 | int i; | |
1346 | char *result = NULL; /* (Initialization only needed to keep | |
1347 | * the compiler from complaining) */ | |
1348 | ||
1349 | if (argc < 3) { | |
1350 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1351 | argv[0], " varName value ?value ...?\"", (char *) NULL); | |
1352 | return TCL_ERROR; | |
1353 | } | |
1354 | ||
1355 | for (i = 2; i < argc; i++) { | |
1356 | result = Tcl_SetVar(interp, argv[1], argv[i], | |
1357 | TCL_APPEND_VALUE|TCL_LEAVE_ERR_MSG); | |
1358 | if (result == NULL) { | |
1359 | return TCL_ERROR; | |
1360 | } | |
1361 | } | |
1362 | interp->result = result; | |
1363 | return TCL_OK; | |
1364 | } | |
1365 | \f | |
1366 | /* | |
1367 | *---------------------------------------------------------------------- | |
1368 | * | |
1369 | * Tcl_LappendCmd -- | |
1370 | * | |
1371 | * This procedure is invoked to process the "lappend" Tcl command. | |
1372 | * See the user documentation for details on what it does. | |
1373 | * | |
1374 | * Results: | |
1375 | * A standard Tcl result value. | |
1376 | * | |
1377 | * Side effects: | |
1378 | * A variable's value may be changed. | |
1379 | * | |
1380 | *---------------------------------------------------------------------- | |
1381 | */ | |
1382 | ||
1383 | /* ARGSUSED */ | |
1384 | int | |
1385 | Tcl_LappendCmd(dummy, interp, argc, argv) | |
1386 | ClientData dummy; /* Not used. */ | |
1387 | register Tcl_Interp *interp; /* Current interpreter. */ | |
1388 | int argc; /* Number of arguments. */ | |
1389 | char **argv; /* Argument strings. */ | |
1390 | { | |
1391 | int i; | |
1392 | char *result = NULL; /* (Initialization only needed to keep | |
1393 | * the compiler from complaining) */ | |
1394 | ||
1395 | if (argc < 3) { | |
1396 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1397 | argv[0], " varName value ?value ...?\"", (char *) NULL); | |
1398 | return TCL_ERROR; | |
1399 | } | |
1400 | ||
1401 | for (i = 2; i < argc; i++) { | |
1402 | result = Tcl_SetVar(interp, argv[1], argv[i], | |
1403 | TCL_APPEND_VALUE|TCL_LIST_ELEMENT|TCL_LEAVE_ERR_MSG); | |
1404 | if (result == NULL) { | |
1405 | return TCL_ERROR; | |
1406 | } | |
1407 | } | |
1408 | interp->result = result; | |
1409 | return TCL_OK; | |
1410 | } | |
1411 | \f | |
1412 | /* | |
1413 | *---------------------------------------------------------------------- | |
1414 | * | |
1415 | * Tcl_ArrayCmd -- | |
1416 | * | |
1417 | * This procedure is invoked to process the "array" Tcl command. | |
1418 | * See the user documentation for details on what it does. | |
1419 | * | |
1420 | * Results: | |
1421 | * A standard Tcl result value. | |
1422 | * | |
1423 | * Side effects: | |
1424 | * See the user documentation. | |
1425 | * | |
1426 | *---------------------------------------------------------------------- | |
1427 | */ | |
1428 | ||
1429 | /* ARGSUSED */ | |
1430 | int | |
1431 | Tcl_ArrayCmd(dummy, interp, argc, argv) | |
1432 | ClientData dummy; /* Not used. */ | |
1433 | register Tcl_Interp *interp; /* Current interpreter. */ | |
1434 | int argc; /* Number of arguments. */ | |
1435 | char **argv; /* Argument strings. */ | |
1436 | { | |
1437 | int length; | |
1438 | char c; | |
1439 | Var *varPtr; | |
1440 | Tcl_HashEntry *hPtr; | |
1441 | Interp *iPtr = (Interp *) interp; | |
1442 | ||
1443 | if (argc < 3) { | |
1444 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1445 | argv[0], " option arrayName ?arg ...?\"", (char *) NULL); | |
1446 | return TCL_ERROR; | |
1447 | } | |
1448 | ||
1449 | /* | |
1450 | * Locate the array variable (and it better be an array). | |
1451 | */ | |
1452 | ||
1453 | if (iPtr->varFramePtr == NULL) { | |
1454 | hPtr = Tcl_FindHashEntry(&iPtr->globalTable, argv[2]); | |
1455 | } else { | |
1456 | hPtr = Tcl_FindHashEntry(&iPtr->varFramePtr->varTable, argv[2]); | |
1457 | } | |
1458 | if (hPtr == NULL) { | |
1459 | notArray: | |
1460 | Tcl_AppendResult(interp, "\"", argv[2], "\" isn't an array", | |
1461 | (char *) NULL); | |
1462 | return TCL_ERROR; | |
1463 | } | |
1464 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1465 | if (varPtr->flags & VAR_UPVAR) { | |
1466 | varPtr = (Var *) Tcl_GetHashValue(varPtr->value.upvarPtr); | |
1467 | } | |
1468 | if (!(varPtr->flags & VAR_ARRAY)) { | |
1469 | goto notArray; | |
1470 | } | |
1471 | ||
1472 | /* | |
1473 | * Dispatch based on the option. | |
1474 | */ | |
1475 | ||
1476 | c = argv[1][0]; | |
1477 | length = strlen(argv[1]); | |
1478 | if ((c == 'a') && (strncmp(argv[1], "anymore", length) == 0)) { | |
1479 | ArraySearch *searchPtr; | |
1480 | ||
1481 | if (argc != 4) { | |
1482 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1483 | argv[0], " anymore arrayName searchId\"", (char *) NULL); | |
1484 | return TCL_ERROR; | |
1485 | } | |
1486 | searchPtr = ParseSearchId(interp, varPtr, argv[2], argv[3]); | |
1487 | if (searchPtr == NULL) { | |
1488 | return TCL_ERROR; | |
1489 | } | |
1490 | while (1) { | |
1491 | Var *varPtr2; | |
1492 | ||
1493 | if (searchPtr->nextEntry != NULL) { | |
1494 | varPtr2 = (Var *) Tcl_GetHashValue(searchPtr->nextEntry); | |
1495 | if (!(varPtr2->flags & VAR_UNDEFINED)) { | |
1496 | break; | |
1497 | } | |
1498 | } | |
1499 | searchPtr->nextEntry = Tcl_NextHashEntry(&searchPtr->search); | |
1500 | if (searchPtr->nextEntry == NULL) { | |
1501 | interp->result = "0"; | |
1502 | return TCL_OK; | |
1503 | } | |
1504 | } | |
1505 | interp->result = "1"; | |
1506 | return TCL_OK; | |
1507 | } else if ((c == 'd') && (strncmp(argv[1], "donesearch", length) == 0)) { | |
1508 | ArraySearch *searchPtr, *prevPtr; | |
1509 | ||
1510 | if (argc != 4) { | |
1511 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1512 | argv[0], " donesearch arrayName searchId\"", (char *) NULL); | |
1513 | return TCL_ERROR; | |
1514 | } | |
1515 | searchPtr = ParseSearchId(interp, varPtr, argv[2], argv[3]); | |
1516 | if (searchPtr == NULL) { | |
1517 | return TCL_ERROR; | |
1518 | } | |
1519 | if (varPtr->searchPtr == searchPtr) { | |
1520 | varPtr->searchPtr = searchPtr->nextPtr; | |
1521 | } else { | |
1522 | for (prevPtr = varPtr->searchPtr; ; prevPtr = prevPtr->nextPtr) { | |
1523 | if (prevPtr->nextPtr == searchPtr) { | |
1524 | prevPtr->nextPtr = searchPtr->nextPtr; | |
1525 | break; | |
1526 | } | |
1527 | } | |
1528 | } | |
1529 | ckfree((char *) searchPtr); | |
1530 | } else if ((c == 'n') && (strncmp(argv[1], "names", length) == 0) | |
1531 | && (length >= 2)) { | |
1532 | Tcl_HashSearch search; | |
1533 | Var *varPtr2; | |
1534 | ||
1535 | if (argc != 3) { | |
1536 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1537 | argv[0], " names arrayName\"", (char *) NULL); | |
1538 | return TCL_ERROR; | |
1539 | } | |
1540 | for (hPtr = Tcl_FirstHashEntry(varPtr->value.tablePtr, &search); | |
1541 | hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { | |
1542 | varPtr2 = (Var *) Tcl_GetHashValue(hPtr); | |
1543 | if (varPtr2->flags & VAR_UNDEFINED) { | |
1544 | continue; | |
1545 | } | |
1546 | Tcl_AppendElement(interp, | |
1547 | Tcl_GetHashKey(varPtr->value.tablePtr, hPtr), 0); | |
1548 | } | |
1549 | } else if ((c == 'n') && (strncmp(argv[1], "nextelement", length) == 0) | |
1550 | && (length >= 2)) { | |
1551 | ArraySearch *searchPtr; | |
1552 | Tcl_HashEntry *hPtr; | |
1553 | ||
1554 | if (argc != 4) { | |
1555 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1556 | argv[0], " nextelement arrayName searchId\"", | |
1557 | (char *) NULL); | |
1558 | return TCL_ERROR; | |
1559 | } | |
1560 | searchPtr = ParseSearchId(interp, varPtr, argv[2], argv[3]); | |
1561 | if (searchPtr == NULL) { | |
1562 | return TCL_ERROR; | |
1563 | } | |
1564 | while (1) { | |
1565 | Var *varPtr2; | |
1566 | ||
1567 | hPtr = searchPtr->nextEntry; | |
1568 | if (hPtr == NULL) { | |
1569 | hPtr = Tcl_NextHashEntry(&searchPtr->search); | |
1570 | if (hPtr == NULL) { | |
1571 | return TCL_OK; | |
1572 | } | |
1573 | } else { | |
1574 | searchPtr->nextEntry = NULL; | |
1575 | } | |
1576 | varPtr2 = (Var *) Tcl_GetHashValue(hPtr); | |
1577 | if (!(varPtr2->flags & VAR_UNDEFINED)) { | |
1578 | break; | |
1579 | } | |
1580 | } | |
1581 | interp->result = Tcl_GetHashKey(varPtr->value.tablePtr, hPtr); | |
1582 | } else if ((c == 's') && (strncmp(argv[1], "size", length) == 0) | |
1583 | && (length >= 2)) { | |
1584 | Tcl_HashSearch search; | |
1585 | Var *varPtr2; | |
1586 | int size; | |
1587 | ||
1588 | if (argc != 3) { | |
1589 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1590 | argv[0], " size arrayName\"", (char *) NULL); | |
1591 | return TCL_ERROR; | |
1592 | } | |
1593 | size = 0; | |
1594 | for (hPtr = Tcl_FirstHashEntry(varPtr->value.tablePtr, &search); | |
1595 | hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { | |
1596 | varPtr2 = (Var *) Tcl_GetHashValue(hPtr); | |
1597 | if (varPtr2->flags & VAR_UNDEFINED) { | |
1598 | continue; | |
1599 | } | |
1600 | size++; | |
1601 | } | |
1602 | sprintf(interp->result, "%d", size); | |
1603 | } else if ((c == 's') && (strncmp(argv[1], "startsearch", length) == 0) | |
1604 | && (length >= 2)) { | |
1605 | ArraySearch *searchPtr; | |
1606 | ||
1607 | if (argc != 3) { | |
1608 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
1609 | argv[0], " startsearch arrayName\"", (char *) NULL); | |
1610 | return TCL_ERROR; | |
1611 | } | |
1612 | searchPtr = (ArraySearch *) ckalloc(sizeof(ArraySearch)); | |
1613 | if (varPtr->searchPtr == NULL) { | |
1614 | searchPtr->id = 1; | |
1615 | Tcl_AppendResult(interp, "s-1-", argv[2], (char *) NULL); | |
1616 | } else { | |
1617 | char string[20]; | |
1618 | ||
1619 | searchPtr->id = varPtr->searchPtr->id + 1; | |
1620 | sprintf(string, "%d", searchPtr->id); | |
1621 | Tcl_AppendResult(interp, "s-", string, "-", argv[2], | |
1622 | (char *) NULL); | |
1623 | } | |
1624 | searchPtr->varPtr = varPtr; | |
1625 | searchPtr->nextEntry = Tcl_FirstHashEntry(varPtr->value.tablePtr, | |
1626 | &searchPtr->search); | |
1627 | searchPtr->nextPtr = varPtr->searchPtr; | |
1628 | varPtr->searchPtr = searchPtr; | |
1629 | } else { | |
1630 | Tcl_AppendResult(interp, "bad option \"", argv[1], | |
1631 | "\": should be anymore, donesearch, names, nextelement, ", | |
1632 | "size, or startsearch", (char *) NULL); | |
1633 | return TCL_ERROR; | |
1634 | } | |
1635 | return TCL_OK; | |
1636 | } | |
1637 | \f | |
1638 | /* | |
1639 | *---------------------------------------------------------------------- | |
1640 | * | |
1641 | * Tcl_GlobalCmd -- | |
1642 | * | |
1643 | * This procedure is invoked to process the "global" Tcl command. | |
1644 | * See the user documentation for details on what it does. | |
1645 | * | |
1646 | * Results: | |
1647 | * A standard Tcl result value. | |
1648 | * | |
1649 | * Side effects: | |
1650 | * See the user documentation. | |
1651 | * | |
1652 | *---------------------------------------------------------------------- | |
1653 | */ | |
1654 | ||
1655 | /* ARGSUSED */ | |
1656 | int | |
1657 | Tcl_GlobalCmd(dummy, interp, argc, argv) | |
1658 | ClientData dummy; /* Not used. */ | |
1659 | Tcl_Interp *interp; /* Current interpreter. */ | |
1660 | int argc; /* Number of arguments. */ | |
1661 | char **argv; /* Argument strings. */ | |
1662 | { | |
1663 | Var *varPtr, *gVarPtr; | |
1664 | register Interp *iPtr = (Interp *) interp; | |
1665 | Tcl_HashEntry *hPtr, *hPtr2; | |
1666 | int new; | |
1667 | ||
1668 | if (argc < 2) { | |
1669 | Tcl_AppendResult((Tcl_Interp *) iPtr, "wrong # args: should be \"", | |
1670 | argv[0], " varName ?varName ...?\"", (char *) NULL); | |
1671 | return TCL_ERROR; | |
1672 | } | |
1673 | if (iPtr->varFramePtr == NULL) { | |
1674 | return TCL_OK; | |
1675 | } | |
1676 | ||
1677 | for (argc--, argv++; argc > 0; argc--, argv++) { | |
1678 | hPtr = Tcl_CreateHashEntry(&iPtr->globalTable, *argv, &new); | |
1679 | if (new) { | |
1680 | gVarPtr = NewVar(0); | |
1681 | gVarPtr->flags |= VAR_UNDEFINED; | |
1682 | Tcl_SetHashValue(hPtr, gVarPtr); | |
1683 | } else { | |
1684 | gVarPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1685 | } | |
1686 | hPtr2 = Tcl_CreateHashEntry(&iPtr->varFramePtr->varTable, *argv, &new); | |
1687 | if (!new) { | |
1688 | Var *varPtr; | |
1689 | varPtr = (Var *) Tcl_GetHashValue(hPtr2); | |
1690 | if (varPtr->flags & VAR_UPVAR) { | |
1691 | continue; | |
1692 | } else { | |
1693 | Tcl_AppendResult((Tcl_Interp *) iPtr, "variable \"", *argv, | |
1694 | "\" already exists", (char *) NULL); | |
1695 | return TCL_ERROR; | |
1696 | } | |
1697 | } | |
1698 | varPtr = NewVar(0); | |
1699 | varPtr->flags |= VAR_UPVAR; | |
1700 | varPtr->value.upvarPtr = hPtr; | |
1701 | gVarPtr->upvarUses++; | |
1702 | Tcl_SetHashValue(hPtr2, varPtr); | |
1703 | } | |
1704 | return TCL_OK; | |
1705 | } | |
1706 | \f | |
1707 | /* | |
1708 | *---------------------------------------------------------------------- | |
1709 | * | |
1710 | * Tcl_UpvarCmd -- | |
1711 | * | |
1712 | * This procedure is invoked to process the "upvar" Tcl command. | |
1713 | * See the user documentation for details on what it does. | |
1714 | * | |
1715 | * Results: | |
1716 | * A standard Tcl result value. | |
1717 | * | |
1718 | * Side effects: | |
1719 | * See the user documentation. | |
1720 | * | |
1721 | *---------------------------------------------------------------------- | |
1722 | */ | |
1723 | ||
1724 | /* ARGSUSED */ | |
1725 | int | |
1726 | Tcl_UpvarCmd(dummy, interp, argc, argv) | |
1727 | ClientData dummy; /* Not used. */ | |
1728 | Tcl_Interp *interp; /* Current interpreter. */ | |
1729 | int argc; /* Number of arguments. */ | |
1730 | char **argv; /* Argument strings. */ | |
1731 | { | |
1732 | register Interp *iPtr = (Interp *) interp; | |
1733 | int result; | |
1734 | CallFrame *framePtr; | |
1735 | Var *varPtr = NULL; | |
1736 | Tcl_HashTable *upVarTablePtr; | |
1737 | Tcl_HashEntry *hPtr, *hPtr2; | |
1738 | int new; | |
1739 | Var *upVarPtr; | |
1740 | ||
1741 | if (argc < 3) { | |
1742 | upvarSyntax: | |
1743 | Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], | |
1744 | " ?level? otherVar localVar ?otherVar localVar ...?\"", | |
1745 | (char *) NULL); | |
1746 | return TCL_ERROR; | |
1747 | } | |
1748 | ||
1749 | /* | |
1750 | * Find the hash table containing the variable being referenced. | |
1751 | */ | |
1752 | ||
1753 | result = TclGetFrame(interp, argv[1], &framePtr); | |
1754 | if (result == -1) { | |
1755 | return TCL_ERROR; | |
1756 | } | |
1757 | argc -= result+1; | |
1758 | argv += result+1; | |
1759 | if (framePtr == NULL) { | |
1760 | upVarTablePtr = &iPtr->globalTable; | |
1761 | } else { | |
1762 | upVarTablePtr = &framePtr->varTable; | |
1763 | } | |
1764 | ||
1765 | if ((argc & 1) != 0) { | |
1766 | goto upvarSyntax; | |
1767 | } | |
1768 | ||
1769 | /* | |
1770 | * Iterate over all the pairs of (local variable, other variable) | |
1771 | * names. For each pair, create a hash table entry in the upper | |
1772 | * context (if the name wasn't there already), then associate it | |
1773 | * with a new local variable. | |
1774 | */ | |
1775 | ||
1776 | while (argc > 0) { | |
1777 | hPtr = Tcl_CreateHashEntry(upVarTablePtr, argv[0], &new); | |
1778 | if (new) { | |
1779 | upVarPtr = NewVar(0); | |
1780 | upVarPtr->flags |= VAR_UNDEFINED; | |
1781 | Tcl_SetHashValue(hPtr, upVarPtr); | |
1782 | } else { | |
1783 | upVarPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1784 | if (upVarPtr->flags & VAR_UPVAR) { | |
1785 | hPtr = upVarPtr->value.upvarPtr; | |
1786 | upVarPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1787 | } | |
1788 | } | |
1789 | ||
1790 | hPtr2 = Tcl_CreateHashEntry(&iPtr->varFramePtr->varTable, | |
1791 | argv[1], &new); | |
1792 | if (!new) { | |
1793 | Tcl_AppendResult((Tcl_Interp *) iPtr, "variable \"", argv[1], | |
1794 | "\" already exists", (char *) NULL); | |
1795 | return TCL_ERROR; | |
1796 | } | |
1797 | varPtr = NewVar(0); | |
1798 | varPtr->flags |= VAR_UPVAR; | |
1799 | varPtr->value.upvarPtr = hPtr; | |
1800 | upVarPtr->upvarUses++; | |
1801 | Tcl_SetHashValue(hPtr2, varPtr); | |
1802 | ||
1803 | argc -= 2; | |
1804 | argv += 2; | |
1805 | } | |
1806 | return TCL_OK; | |
1807 | } | |
1808 | \f | |
1809 | /* | |
1810 | *---------------------------------------------------------------------- | |
1811 | * | |
1812 | * TclDeleteVars -- | |
1813 | * | |
1814 | * This procedure is called to recycle all the storage space | |
1815 | * associated with a table of variables. For this procedure | |
1816 | * to work correctly, it must not be possible for any of the | |
1817 | * variable in the table to be accessed from Tcl commands | |
1818 | * (e.g. from trace procedures). | |
1819 | * | |
1820 | * Results: | |
1821 | * None. | |
1822 | * | |
1823 | * Side effects: | |
1824 | * Variables are deleted and trace procedures are invoked, if | |
1825 | * any are declared. | |
1826 | * | |
1827 | *---------------------------------------------------------------------- | |
1828 | */ | |
1829 | ||
1830 | void | |
1831 | TclDeleteVars(iPtr, tablePtr) | |
1832 | Interp *iPtr; /* Interpreter to which variables belong. */ | |
1833 | Tcl_HashTable *tablePtr; /* Hash table containing variables to | |
1834 | * delete. */ | |
1835 | { | |
1836 | Tcl_HashSearch search; | |
1837 | Tcl_HashEntry *hPtr; | |
1838 | register Var *varPtr; | |
1839 | int flags, globalFlag; | |
1840 | ||
1841 | flags = TCL_TRACE_UNSETS; | |
1842 | if (tablePtr == &iPtr->globalTable) { | |
1843 | flags |= TCL_INTERP_DESTROYED | TCL_GLOBAL_ONLY; | |
1844 | } | |
1845 | for (hPtr = Tcl_FirstHashEntry(tablePtr, &search); hPtr != NULL; | |
1846 | hPtr = Tcl_NextHashEntry(&search)) { | |
1847 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1848 | ||
1849 | /* | |
1850 | * For global/upvar variables referenced in procedures, free up the | |
1851 | * local space and then decrement the reference count on the | |
1852 | * variable referred to. If there are no more references to the | |
1853 | * global/upvar and it is undefined and has no traces set, then | |
1854 | * follow on and delete the referenced variable too. | |
1855 | */ | |
1856 | ||
1857 | globalFlag = 0; | |
1858 | if (varPtr->flags & VAR_UPVAR) { | |
1859 | hPtr = varPtr->value.upvarPtr; | |
1860 | ckfree((char *) varPtr); | |
1861 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1862 | varPtr->upvarUses--; | |
1863 | if ((varPtr->upvarUses != 0) || !(varPtr->flags & VAR_UNDEFINED) | |
1864 | || (varPtr->tracePtr != NULL)) { | |
1865 | continue; | |
1866 | } | |
1867 | globalFlag = TCL_GLOBAL_ONLY; | |
1868 | } | |
1869 | ||
1870 | /* | |
1871 | * Invoke traces on the variable that is being deleted, then | |
1872 | * free up the variable's space (no need to free the hash entry | |
1873 | * here, unless we're dealing with a global variable: the | |
1874 | * hash entries will be deleted automatically when the whole | |
1875 | * table is deleted). | |
1876 | */ | |
1877 | ||
1878 | if (varPtr->tracePtr != NULL) { | |
1879 | (void) CallTraces(iPtr, (Var *) NULL, hPtr, | |
1880 | Tcl_GetHashKey(tablePtr, hPtr), (char *) NULL, | |
1881 | flags | globalFlag); | |
1882 | while (varPtr->tracePtr != NULL) { | |
1883 | VarTrace *tracePtr = varPtr->tracePtr; | |
1884 | varPtr->tracePtr = tracePtr->nextPtr; | |
1885 | ckfree((char *) tracePtr); | |
1886 | } | |
1887 | } | |
1888 | if (varPtr->flags & VAR_ARRAY) { | |
1889 | DeleteArray(iPtr, Tcl_GetHashKey(tablePtr, hPtr), varPtr, | |
1890 | flags | globalFlag); | |
1891 | } | |
1892 | if (globalFlag) { | |
1893 | Tcl_DeleteHashEntry(hPtr); | |
1894 | } | |
1895 | ckfree((char *) varPtr); | |
1896 | } | |
1897 | Tcl_DeleteHashTable(tablePtr); | |
1898 | } | |
1899 | \f | |
1900 | /* | |
1901 | *---------------------------------------------------------------------- | |
1902 | * | |
1903 | * CallTraces -- | |
1904 | * | |
1905 | * This procedure is invoked to find and invoke relevant | |
1906 | * trace procedures associated with a particular operation on | |
1907 | * a variable. This procedure invokes traces both on the | |
1908 | * variable and on its containing array (where relevant). | |
1909 | * | |
1910 | * Results: | |
1911 | * The return value is NULL if no trace procedures were invoked, or | |
1912 | * if all the invoked trace procedures returned successfully. | |
1913 | * The return value is non-zero if a trace procedure returned an | |
1914 | * error (in this case no more trace procedures were invoked after | |
1915 | * the error was returned). In this case the return value is a | |
1916 | * pointer to a static string describing the error. | |
1917 | * | |
1918 | * Side effects: | |
1919 | * Almost anything can happen, depending on trace; this procedure | |
1920 | * itself doesn't have any side effects. | |
1921 | * | |
1922 | *---------------------------------------------------------------------- | |
1923 | */ | |
1924 | ||
1925 | static char * | |
1926 | CallTraces(iPtr, arrayPtr, hPtr, name1, name2, flags) | |
1927 | Interp *iPtr; /* Interpreter containing variable. */ | |
1928 | register Var *arrayPtr; /* Pointer to array variable that | |
1929 | * contains the variable, or NULL if | |
1930 | * the variable isn't an element of an | |
1931 | * array. */ | |
1932 | Tcl_HashEntry *hPtr; /* Hash table entry corresponding to | |
1933 | * variable whose traces are to be | |
1934 | * invoked. */ | |
1935 | char *name1, *name2; /* Variable's two-part name. */ | |
1936 | int flags; /* Flags to pass to trace procedures: | |
1937 | * indicates what's happening to | |
1938 | * variable, plus other stuff like | |
1939 | * TCL_GLOBAL_ONLY and | |
1940 | * TCL_INTERP_DESTROYED. */ | |
1941 | { | |
1942 | Var *varPtr; | |
1943 | register VarTrace *tracePtr; | |
1944 | ActiveVarTrace active; | |
1945 | char *result; | |
1946 | int savedArrayFlags = 0; /* (Initialization not needed except | |
1947 | * to prevent compiler warning) */ | |
1948 | ||
1949 | /* | |
1950 | * If there are already similar trace procedures active for the | |
1951 | * variable, don't call them again. | |
1952 | */ | |
1953 | ||
1954 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
1955 | if (varPtr->flags & VAR_TRACE_ACTIVE) { | |
1956 | return NULL; | |
1957 | } | |
1958 | varPtr->flags |= VAR_TRACE_ACTIVE; | |
1959 | ||
1960 | /* | |
1961 | * Invoke traces on the array containing the variable, if relevant. | |
1962 | */ | |
1963 | ||
1964 | result = NULL; | |
1965 | active.nextPtr = iPtr->activeTracePtr; | |
1966 | iPtr->activeTracePtr = &active; | |
1967 | if (arrayPtr != NULL) { | |
1968 | savedArrayFlags = arrayPtr->flags; | |
1969 | arrayPtr->flags |= VAR_ELEMENT_ACTIVE; | |
1970 | for (tracePtr = arrayPtr->tracePtr; tracePtr != NULL; | |
1971 | tracePtr = active.nextTracePtr) { | |
1972 | active.nextTracePtr = tracePtr->nextPtr; | |
1973 | if (!(tracePtr->flags & flags)) { | |
1974 | continue; | |
1975 | } | |
1976 | result = (*tracePtr->traceProc)(tracePtr->clientData, | |
1977 | (Tcl_Interp *) iPtr, name1, name2, flags); | |
1978 | if (result != NULL) { | |
1979 | if (flags & TCL_TRACE_UNSETS) { | |
1980 | result = NULL; | |
1981 | } else { | |
1982 | goto done; | |
1983 | } | |
1984 | } | |
1985 | } | |
1986 | } | |
1987 | ||
1988 | /* | |
1989 | * Invoke traces on the variable itself. | |
1990 | */ | |
1991 | ||
1992 | if (flags & TCL_TRACE_UNSETS) { | |
1993 | flags |= TCL_TRACE_DESTROYED; | |
1994 | } | |
1995 | for (tracePtr = varPtr->tracePtr; tracePtr != NULL; | |
1996 | tracePtr = active.nextTracePtr) { | |
1997 | active.nextTracePtr = tracePtr->nextPtr; | |
1998 | if (!(tracePtr->flags & flags)) { | |
1999 | continue; | |
2000 | } | |
2001 | result = (*tracePtr->traceProc)(tracePtr->clientData, | |
2002 | (Tcl_Interp *) iPtr, name1, name2, flags); | |
2003 | if (result != NULL) { | |
2004 | if (flags & TCL_TRACE_UNSETS) { | |
2005 | result = NULL; | |
2006 | } else { | |
2007 | goto done; | |
2008 | } | |
2009 | } | |
2010 | } | |
2011 | ||
2012 | /* | |
2013 | * Restore the variable's flags, remove the record of our active | |
2014 | * traces, and then return. Remember that the variable could have | |
2015 | * been re-allocated during the traces, but its hash entry won't | |
2016 | * change. | |
2017 | */ | |
2018 | ||
2019 | done: | |
2020 | if (arrayPtr != NULL) { | |
2021 | arrayPtr->flags = savedArrayFlags; | |
2022 | } | |
2023 | varPtr = (Var *) Tcl_GetHashValue(hPtr); | |
2024 | varPtr->flags &= ~VAR_TRACE_ACTIVE; | |
2025 | iPtr->activeTracePtr = active.nextPtr; | |
2026 | return result; | |
2027 | } | |
2028 | \f | |
2029 | /* | |
2030 | *---------------------------------------------------------------------- | |
2031 | * | |
2032 | * NewVar -- | |
2033 | * | |
2034 | * Create a new variable with a given initial value. | |
2035 | * | |
2036 | * Results: | |
2037 | * The return value is a pointer to the new variable structure. | |
2038 | * The variable will not be part of any hash table yet, and its | |
2039 | * upvarUses count is initialized to 0. Its initial value will | |
2040 | * be empty, but "space" bytes will be available in the value | |
2041 | * area. | |
2042 | * | |
2043 | * Side effects: | |
2044 | * Storage gets allocated. | |
2045 | * | |
2046 | *---------------------------------------------------------------------- | |
2047 | */ | |
2048 | ||
2049 | static Var * | |
2050 | NewVar(space) | |
2051 | int space; /* Minimum amount of space to allocate | |
2052 | * for variable's value. */ | |
2053 | { | |
2054 | int extra; | |
2055 | register Var *varPtr; | |
2056 | ||
2057 | extra = space - sizeof(varPtr->value); | |
2058 | if (extra < 0) { | |
2059 | extra = 0; | |
2060 | space = sizeof(varPtr->value); | |
2061 | } | |
2062 | varPtr = (Var *) ckalloc((unsigned) (sizeof(Var) + extra)); | |
2063 | varPtr->valueLength = 0; | |
2064 | varPtr->valueSpace = space; | |
2065 | varPtr->upvarUses = 0; | |
2066 | varPtr->tracePtr = NULL; | |
2067 | varPtr->searchPtr = NULL; | |
2068 | varPtr->flags = 0; | |
2069 | varPtr->value.string[0] = 0; | |
2070 | return varPtr; | |
2071 | } | |
2072 | \f | |
2073 | /* | |
2074 | *---------------------------------------------------------------------- | |
2075 | * | |
2076 | * ParseSearchId -- | |
2077 | * | |
2078 | * This procedure translates from a string to a pointer to an | |
2079 | * active array search (if there is one that matches the string). | |
2080 | * | |
2081 | * Results: | |
2082 | * The return value is a pointer to the array search indicated | |
2083 | * by string, or NULL if there isn't one. If NULL is returned, | |
2084 | * interp->result contains an error message. | |
2085 | * | |
2086 | * Side effects: | |
2087 | * None. | |
2088 | * | |
2089 | *---------------------------------------------------------------------- | |
2090 | */ | |
2091 | ||
2092 | static ArraySearch * | |
2093 | ParseSearchId(interp, varPtr, varName, string) | |
2094 | Tcl_Interp *interp; /* Interpreter containing variable. */ | |
2095 | Var *varPtr; /* Array variable search is for. */ | |
2096 | char *varName; /* Name of array variable that search is | |
2097 | * supposed to be for. */ | |
2098 | char *string; /* String containing id of search. Must have | |
2099 | * form "search-num-var" where "num" is a | |
2100 | * decimal number and "var" is a variable | |
2101 | * name. */ | |
2102 | { | |
2103 | char *end; | |
2104 | int id; | |
2105 | ArraySearch *searchPtr; | |
2106 | ||
2107 | /* | |
2108 | * Parse the id into the three parts separated by dashes. | |
2109 | */ | |
2110 | ||
2111 | if ((string[0] != 's') || (string[1] != '-')) { | |
2112 | syntax: | |
2113 | Tcl_AppendResult(interp, "illegal search identifier \"", string, | |
2114 | "\"", (char *) NULL); | |
2115 | return NULL; | |
2116 | } | |
2117 | id = strtoul(string+2, &end, 10); | |
2118 | if ((end == (string+2)) || (*end != '-')) { | |
2119 | goto syntax; | |
2120 | } | |
2121 | if (strcmp(end+1, varName) != 0) { | |
2122 | Tcl_AppendResult(interp, "search identifier \"", string, | |
2123 | "\" isn't for variable \"", varName, "\"", (char *) NULL); | |
2124 | return NULL; | |
2125 | } | |
2126 | ||
2127 | /* | |
2128 | * Search through the list of active searches on the interpreter | |
2129 | * to see if the desired one exists. | |
2130 | */ | |
2131 | ||
2132 | for (searchPtr = varPtr->searchPtr; searchPtr != NULL; | |
2133 | searchPtr = searchPtr->nextPtr) { | |
2134 | if (searchPtr->id == id) { | |
2135 | return searchPtr; | |
2136 | } | |
2137 | } | |
2138 | Tcl_AppendResult(interp, "couldn't find search \"", string, "\"", | |
2139 | (char *) NULL); | |
2140 | return NULL; | |
2141 | } | |
2142 | \f | |
2143 | /* | |
2144 | *---------------------------------------------------------------------- | |
2145 | * | |
2146 | * DeleteSearches -- | |
2147 | * | |
2148 | * This procedure is called to free up all of the searches | |
2149 | * associated with an array variable. | |
2150 | * | |
2151 | * Results: | |
2152 | * None. | |
2153 | * | |
2154 | * Side effects: | |
2155 | * Memory is released to the storage allocator. | |
2156 | * | |
2157 | *---------------------------------------------------------------------- | |
2158 | */ | |
2159 | ||
2160 | static void | |
2161 | DeleteSearches(arrayVarPtr) | |
2162 | register Var *arrayVarPtr; /* Variable whose searches are | |
2163 | * to be deleted. */ | |
2164 | { | |
2165 | ArraySearch *searchPtr; | |
2166 | ||
2167 | while (arrayVarPtr->searchPtr != NULL) { | |
2168 | searchPtr = arrayVarPtr->searchPtr; | |
2169 | arrayVarPtr->searchPtr = searchPtr->nextPtr; | |
2170 | ckfree((char *) searchPtr); | |
2171 | } | |
2172 | } | |
2173 | \f | |
2174 | /* | |
2175 | *---------------------------------------------------------------------- | |
2176 | * | |
2177 | * DeleteArray -- | |
2178 | * | |
2179 | * This procedure is called to free up everything in an array | |
2180 | * variable. It's the caller's responsibility to make sure | |
2181 | * that the array is no longer accessible before this procedure | |
2182 | * is called. | |
2183 | * | |
2184 | * Results: | |
2185 | * None. | |
2186 | * | |
2187 | * Side effects: | |
2188 | * All storage associated with varPtr's array elements is deleted | |
2189 | * (including the hash table). Any delete trace procedures for | |
2190 | * array elements are invoked. | |
2191 | * | |
2192 | *---------------------------------------------------------------------- | |
2193 | */ | |
2194 | ||
2195 | static void | |
2196 | DeleteArray(iPtr, arrayName, varPtr, flags) | |
2197 | Interp *iPtr; /* Interpreter containing array. */ | |
2198 | char *arrayName; /* Name of array (used for trace | |
2199 | * callbacks). */ | |
2200 | Var *varPtr; /* Pointer to variable structure. */ | |
2201 | int flags; /* Flags to pass to CallTraces: | |
2202 | * TCL_TRACE_UNSETS and sometimes | |
2203 | * TCL_INTERP_DESTROYED and/or | |
2204 | * TCL_GLOBAL_ONLY. */ | |
2205 | { | |
2206 | Tcl_HashSearch search; | |
2207 | register Tcl_HashEntry *hPtr; | |
2208 | register Var *elPtr; | |
2209 | ||
2210 | DeleteSearches(varPtr); | |
2211 | for (hPtr = Tcl_FirstHashEntry(varPtr->value.tablePtr, &search); | |
2212 | hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { | |
2213 | elPtr = (Var *) Tcl_GetHashValue(hPtr); | |
2214 | if (elPtr->tracePtr != NULL) { | |
2215 | (void) CallTraces(iPtr, (Var *) NULL, hPtr, arrayName, | |
2216 | Tcl_GetHashKey(varPtr->value.tablePtr, hPtr), flags); | |
2217 | while (elPtr->tracePtr != NULL) { | |
2218 | VarTrace *tracePtr = elPtr->tracePtr; | |
2219 | elPtr->tracePtr = tracePtr->nextPtr; | |
2220 | ckfree((char *) tracePtr); | |
2221 | } | |
2222 | } | |
2223 | if (elPtr->flags & VAR_SEARCHES_POSSIBLE) { | |
2224 | panic("DeleteArray found searches on array alement!"); | |
2225 | } | |
2226 | ckfree((char *) elPtr); | |
2227 | } | |
2228 | Tcl_DeleteHashTable(varPtr->value.tablePtr); | |
2229 | ckfree((char *) varPtr->value.tablePtr); | |
2230 | } | |
2231 | \f | |
2232 | /* | |
2233 | *---------------------------------------------------------------------- | |
2234 | * | |
2235 | * VarErrMsg -- | |
2236 | * | |
2237 | * Generate a reasonable error message describing why a variable | |
2238 | * operation failed. | |
2239 | * | |
2240 | * Results: | |
2241 | * None. | |
2242 | * | |
2243 | * Side effects: | |
2244 | * Interp->result is reset to hold a message identifying the | |
2245 | * variable given by name1 and name2 and describing why the | |
2246 | * variable operation failed. | |
2247 | * | |
2248 | *---------------------------------------------------------------------- | |
2249 | */ | |
2250 | ||
2251 | static void | |
2252 | VarErrMsg(interp, name1, name2, operation, reason) | |
2253 | Tcl_Interp *interp; /* Interpreter in which to record message. */ | |
2254 | char *name1, *name2; /* Variable's two-part name. */ | |
2255 | char *operation; /* String describing operation that failed, | |
2256 | * e.g. "read", "set", or "unset". */ | |
2257 | char *reason; /* String describing why operation failed. */ | |
2258 | { | |
2259 | Tcl_ResetResult(interp); | |
2260 | Tcl_AppendResult(interp, "can't ", operation, " \"", name1, (char *) NULL); | |
2261 | if (name2 != NULL) { | |
2262 | Tcl_AppendResult(interp, "(", name2, ")", (char *) NULL); | |
2263 | } | |
2264 | Tcl_AppendResult(interp, "\": ", reason, (char *) NULL); | |
2265 | } |