home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume25 / tcl / part22 < prev    next >
Encoding:
Text File  |  1991-11-15  |  33.3 KB  |  1,183 lines

  1. Newsgroups: comp.sources.misc
  2. From: karl@sugar.neosoft.com (Karl Lehenbauer)
  3. Subject:  v25i090:  tcl - tool command language, version 6.1, Part22/33
  4. Message-ID: <1991Nov15.225200.21168@sparky.imd.sterling.com>
  5. X-Md4-Signature: a08a34334a783d96d89dbc9675e9c035
  6. Date: Fri, 15 Nov 1991 22:52:00 GMT
  7. Approved: kent@sparky.imd.sterling.com
  8.  
  9. Submitted-by: karl@sugar.neosoft.com (Karl Lehenbauer)
  10. Posting-number: Volume 25, Issue 90
  11. Archive-name: tcl/part22
  12. Environment: UNIX
  13.  
  14. #! /bin/sh
  15. # This is a shell archive.  Remove anything before this line, then unpack
  16. # it by saving it into a file and typing "sh file".  To overwrite existing
  17. # files, type "sh file -c".  You can also feed this as standard input via
  18. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  19. # will see the following message at the end:
  20. #        "End of archive 22 (of 33)."
  21. # Contents:  tcl6.1/tclVar.c.2
  22. # Wrapped by karl@one on Tue Nov 12 19:44:28 1991
  23. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  24. if test -f 'tcl6.1/tclVar.c.2' -a "${1}" != "-c" ; then 
  25.   echo shar: Will not clobber existing file \"'tcl6.1/tclVar.c.2'\"
  26. else
  27. echo shar: Extracting \"'tcl6.1/tclVar.c.2'\" \(30825 characters\)
  28. sed "s/^X//" >'tcl6.1/tclVar.c.2' <<'END_OF_FILE'
  29. X
  30. X/*
  31. X *----------------------------------------------------------------------
  32. X *
  33. X * Tcl_VarTraceInfo2 --
  34. X *
  35. X *    Same as Tcl_VarTraceInfo, except takes name in two pieces
  36. X *    instead of one.
  37. X *
  38. X * Results:
  39. X *    Same as Tcl_VarTraceInfo.
  40. X *
  41. X * Side effects:
  42. X *    None.
  43. X *
  44. X *----------------------------------------------------------------------
  45. X */
  46. X
  47. XClientData
  48. XTcl_VarTraceInfo2(interp, name1, name2, flags, proc, prevClientData)
  49. X    Tcl_Interp *interp;        /* Interpreter containing variable. */
  50. X    char *name1;        /* Name of variable or array. */
  51. X    char *name2;        /* Name of element within array;  NULL means
  52. X                 * trace applies to scalar variable or array
  53. X                 * as-a-whole. */
  54. X    int flags;            /* 0 or TCL_GLOBAL_ONLY. */
  55. X    Tcl_VarTraceProc *proc;    /* Procedure assocated with trace. */
  56. X    ClientData prevClientData;    /* If non-NULL, gives last value returned
  57. X                 * by this procedure, so this call will
  58. X                 * return the next trace after that one.
  59. X                 * If NULL, this call will return the
  60. X                 * first trace. */
  61. X{
  62. X    register VarTrace *tracePtr;
  63. X    Var *varPtr;
  64. X    Interp *iPtr = (Interp *) interp;
  65. X    Tcl_HashEntry *hPtr;
  66. X
  67. X    /*
  68. X     * First, lookup the variable.
  69. X     */
  70. X
  71. X    if ((flags & TCL_GLOBAL_ONLY) || (iPtr->varFramePtr == NULL)) {
  72. X    hPtr = Tcl_FindHashEntry(&iPtr->globalTable, name1);
  73. X    } else {
  74. X    hPtr = Tcl_FindHashEntry(&iPtr->varFramePtr->varTable, name1);
  75. X    }
  76. X    if (hPtr == NULL) {
  77. X    return NULL;
  78. X    }
  79. X    varPtr = (Var *) Tcl_GetHashValue(hPtr);
  80. X    if (varPtr->flags & VAR_UPVAR) {
  81. X    hPtr = varPtr->value.upvarPtr;
  82. X    varPtr = (Var *) Tcl_GetHashValue(hPtr);
  83. X    }
  84. X    if (name2 != NULL) {
  85. X    if (!(varPtr->flags & VAR_ARRAY)) {
  86. X        return NULL;
  87. X    }
  88. X    hPtr = Tcl_FindHashEntry(varPtr->value.tablePtr, name2);
  89. X    if (hPtr == NULL) {
  90. X        return NULL;
  91. X    }
  92. X    varPtr = (Var *) Tcl_GetHashValue(hPtr);
  93. X    }
  94. X
  95. X    /*
  96. X     * Find the relevant trace, if any, and return its clientData.
  97. X     */
  98. X
  99. X    tracePtr = varPtr->tracePtr;
  100. X    if (prevClientData != NULL) {
  101. X    for ( ; tracePtr != NULL; tracePtr = tracePtr->nextPtr) {
  102. X        if ((tracePtr->clientData == prevClientData)
  103. X            && (tracePtr->traceProc == proc)) {
  104. X        tracePtr = tracePtr->nextPtr;
  105. X        break;
  106. X        }
  107. X    }
  108. X    }
  109. X    for ( ; tracePtr != NULL; tracePtr = tracePtr->nextPtr) {
  110. X    if (tracePtr->traceProc == proc) {
  111. X        return tracePtr->clientData;
  112. X    }
  113. X    }
  114. X    return NULL;
  115. X}
  116. X
  117. X/*
  118. X *----------------------------------------------------------------------
  119. X *
  120. X * Tcl_SetCmd --
  121. X *
  122. X *    This procedure is invoked to process the "set" Tcl command.
  123. X *    See the user documentation for details on what it does.
  124. X *
  125. X * Results:
  126. X *    A standard Tcl result value.
  127. X *
  128. X * Side effects:
  129. X *    A variable's value may be changed.
  130. X *
  131. X *----------------------------------------------------------------------
  132. X */
  133. X
  134. X    /* ARGSUSED */
  135. Xint
  136. XTcl_SetCmd(dummy, interp, argc, argv)
  137. X    ClientData dummy;            /* Not used. */
  138. X    register Tcl_Interp *interp;    /* Current interpreter. */
  139. X    int argc;                /* Number of arguments. */
  140. X    char **argv;            /* Argument strings. */
  141. X{
  142. X    if (argc == 2) {
  143. X    char *value;
  144. X
  145. X    value = Tcl_GetVar(interp, argv[1], TCL_LEAVE_ERR_MSG);
  146. X    if (value == NULL) {
  147. X        return TCL_ERROR;
  148. X    }
  149. X    interp->result = value;
  150. X    return TCL_OK;
  151. X    } else if (argc == 3) {
  152. X    char *result;
  153. X
  154. X    result = Tcl_SetVar(interp, argv[1], argv[2], TCL_LEAVE_ERR_MSG);
  155. X    if (result == NULL) {
  156. X        return TCL_ERROR;
  157. X    }
  158. X    interp->result = result;
  159. X    return TCL_OK;
  160. X    } else {
  161. X    Tcl_AppendResult(interp, "wrong # args: should be \"",
  162. X        argv[0], " varName ?newValue?\"", (char *) NULL);
  163. X    return TCL_ERROR;
  164. X    }
  165. X}
  166. X
  167. X/*
  168. X *----------------------------------------------------------------------
  169. X *
  170. X * Tcl_UnsetCmd --
  171. X *
  172. X *    This procedure is invoked to process the "unset" Tcl command.
  173. X *    See the user documentation for details on what it does.
  174. X *
  175. X * Results:
  176. X *    A standard Tcl result value.
  177. X *
  178. X * Side effects:
  179. X *    See the user documentation.
  180. X *
  181. X *----------------------------------------------------------------------
  182. X */
  183. X
  184. X    /* ARGSUSED */
  185. Xint
  186. XTcl_UnsetCmd(dummy, interp, argc, argv)
  187. X    ClientData dummy;            /* Not used. */
  188. X    register Tcl_Interp *interp;    /* Current interpreter. */
  189. X    int argc;                /* Number of arguments. */
  190. X    char **argv;            /* Argument strings. */
  191. X{
  192. X    int i;
  193. X
  194. X    if (argc < 2) {
  195. X    Tcl_AppendResult(interp, "wrong # args: should be \"",
  196. X        argv[0], " varName ?varName ...?\"", (char *) NULL);
  197. X    return TCL_ERROR;
  198. X    }
  199. X    for (i = 1; i < argc; i++) {
  200. X    if (Tcl_UnsetVar(interp, argv[i], TCL_LEAVE_ERR_MSG) != 0) {
  201. X        return TCL_ERROR;
  202. X    }
  203. X    }
  204. X    return TCL_OK;
  205. X}
  206. X
  207. X/*
  208. X *----------------------------------------------------------------------
  209. X *
  210. X * Tcl_AppendCmd --
  211. X *
  212. X *    This procedure is invoked to process the "append" Tcl command.
  213. X *    See the user documentation for details on what it does.
  214. X *
  215. X * Results:
  216. X *    A standard Tcl result value.
  217. X *
  218. X * Side effects:
  219. X *    A variable's value may be changed.
  220. X *
  221. X *----------------------------------------------------------------------
  222. X */
  223. X
  224. X    /* ARGSUSED */
  225. Xint
  226. XTcl_AppendCmd(dummy, interp, argc, argv)
  227. X    ClientData dummy;            /* Not used. */
  228. X    register Tcl_Interp *interp;    /* Current interpreter. */
  229. X    int argc;                /* Number of arguments. */
  230. X    char **argv;            /* Argument strings. */
  231. X{
  232. X    int i;
  233. X    char *result = NULL;        /* (Initialization only needed to keep
  234. X                     * the compiler from complaining) */
  235. X
  236. X    if (argc < 3) {
  237. X    Tcl_AppendResult(interp, "wrong # args: should be \"",
  238. X        argv[0], " varName value ?value ...?\"", (char *) NULL);
  239. X    return TCL_ERROR;
  240. X    }
  241. X
  242. X    for (i = 2; i < argc; i++) {
  243. X    result = Tcl_SetVar(interp, argv[1], argv[i],
  244. X        TCL_APPEND_VALUE|TCL_LEAVE_ERR_MSG);
  245. X    if (result == NULL) {
  246. X        return TCL_ERROR;
  247. X    }
  248. X    }
  249. X    interp->result = result;
  250. X    return TCL_OK;
  251. X}
  252. X
  253. X/*
  254. X *----------------------------------------------------------------------
  255. X *
  256. X * Tcl_LappendCmd --
  257. X *
  258. X *    This procedure is invoked to process the "lappend" Tcl command.
  259. X *    See the user documentation for details on what it does.
  260. X *
  261. X * Results:
  262. X *    A standard Tcl result value.
  263. X *
  264. X * Side effects:
  265. X *    A variable's value may be changed.
  266. X *
  267. X *----------------------------------------------------------------------
  268. X */
  269. X
  270. X    /* ARGSUSED */
  271. Xint
  272. XTcl_LappendCmd(dummy, interp, argc, argv)
  273. X    ClientData dummy;            /* Not used. */
  274. X    register Tcl_Interp *interp;    /* Current interpreter. */
  275. X    int argc;                /* Number of arguments. */
  276. X    char **argv;            /* Argument strings. */
  277. X{
  278. X    int i;
  279. X    char *result = NULL;        /* (Initialization only needed to keep
  280. X                     * the compiler from complaining) */
  281. X
  282. X    if (argc < 3) {
  283. X    Tcl_AppendResult(interp, "wrong # args: should be \"",
  284. X        argv[0], " varName value ?value ...?\"", (char *) NULL);
  285. X    return TCL_ERROR;
  286. X    }
  287. X
  288. X    for (i = 2; i < argc; i++) {
  289. X    result = Tcl_SetVar(interp, argv[1], argv[i],
  290. X        TCL_APPEND_VALUE|TCL_LIST_ELEMENT|TCL_LEAVE_ERR_MSG);
  291. X    if (result == NULL) {
  292. X        return TCL_ERROR;
  293. X    }
  294. X    }
  295. X    interp->result = result;
  296. X    return TCL_OK;
  297. X}
  298. X
  299. X/*
  300. X *----------------------------------------------------------------------
  301. X *
  302. X * Tcl_ArrayCmd --
  303. X *
  304. X *    This procedure is invoked to process the "array" Tcl command.
  305. X *    See the user documentation for details on what it does.
  306. X *
  307. X * Results:
  308. X *    A standard Tcl result value.
  309. X *
  310. X * Side effects:
  311. X *    See the user documentation.
  312. X *
  313. X *----------------------------------------------------------------------
  314. X */
  315. X
  316. X    /* ARGSUSED */
  317. Xint
  318. XTcl_ArrayCmd(dummy, interp, argc, argv)
  319. X    ClientData dummy;            /* Not used. */
  320. X    register Tcl_Interp *interp;    /* Current interpreter. */
  321. X    int argc;                /* Number of arguments. */
  322. X    char **argv;            /* Argument strings. */
  323. X{
  324. X    int length;
  325. X    char c;
  326. X    Var *varPtr;
  327. X    Tcl_HashEntry *hPtr;
  328. X    Interp *iPtr = (Interp *) interp;
  329. X
  330. X    if (argc < 3) {
  331. X    Tcl_AppendResult(interp, "wrong # args: should be \"",
  332. X        argv[0], " option arrayName ?arg ...?\"", (char *) NULL);
  333. X    return TCL_ERROR;
  334. X    }
  335. X
  336. X    /*
  337. X     * Locate the array variable (and it better be an array).
  338. X     */
  339. X
  340. X    if (iPtr->varFramePtr == NULL) {
  341. X    hPtr = Tcl_FindHashEntry(&iPtr->globalTable, argv[2]);
  342. X    } else {
  343. X    hPtr = Tcl_FindHashEntry(&iPtr->varFramePtr->varTable, argv[2]);
  344. X    }
  345. X    if (hPtr == NULL) {
  346. X    notArray:
  347. X    Tcl_AppendResult(interp, "\"", argv[2], "\" isn't an array",
  348. X        (char *) NULL);
  349. X    return TCL_ERROR;
  350. X    }
  351. X    varPtr = (Var *) Tcl_GetHashValue(hPtr);
  352. X    if (varPtr->flags & VAR_UPVAR) {
  353. X    varPtr = (Var *) Tcl_GetHashValue(varPtr->value.upvarPtr);
  354. X    }
  355. X    if (!(varPtr->flags & VAR_ARRAY)) {
  356. X    goto notArray;
  357. X    }
  358. X
  359. X    /*
  360. X     * Dispatch based on the option.
  361. X     */
  362. X
  363. X    c = argv[1][0];
  364. X    length = strlen(argv[1]);
  365. X    if ((c == 'a') && (strncmp(argv[1], "anymore", length) == 0)) {
  366. X    ArraySearch *searchPtr;
  367. X
  368. X    if (argc != 4) {
  369. X        Tcl_AppendResult(interp, "wrong # args: should be \"",
  370. X            argv[0], " anymore arrayName searchId\"", (char *) NULL);
  371. X        return TCL_ERROR;
  372. X    }
  373. X    searchPtr = ParseSearchId(interp, varPtr, argv[2], argv[3]);
  374. X    if (searchPtr == NULL) {
  375. X        return TCL_ERROR;
  376. X    }
  377. X    while (1) {
  378. X        Var *varPtr2;
  379. X
  380. X        if (searchPtr->nextEntry != NULL) {
  381. X        varPtr2 = (Var *) Tcl_GetHashValue(searchPtr->nextEntry);
  382. X        if (!(varPtr2->flags & VAR_UNDEFINED)) {
  383. X            break;
  384. X        }
  385. X        }
  386. X        searchPtr->nextEntry = Tcl_NextHashEntry(&searchPtr->search);
  387. X        if (searchPtr->nextEntry == NULL) {
  388. X        interp->result = "0";
  389. X        return TCL_OK;
  390. X        }
  391. X    }
  392. X    interp->result = "1";
  393. X    return TCL_OK;
  394. X    } else if ((c == 'd') && (strncmp(argv[1], "donesearch", length) == 0)) {
  395. X    ArraySearch *searchPtr, *prevPtr;
  396. X
  397. X    if (argc != 4) {
  398. X        Tcl_AppendResult(interp, "wrong # args: should be \"",
  399. X            argv[0], " donesearch arrayName searchId\"", (char *) NULL);
  400. X        return TCL_ERROR;
  401. X    }
  402. X    searchPtr = ParseSearchId(interp, varPtr, argv[2], argv[3]);
  403. X    if (searchPtr == NULL) {
  404. X        return TCL_ERROR;
  405. X    }
  406. X    if (varPtr->searchPtr == searchPtr) {
  407. X        varPtr->searchPtr = searchPtr->nextPtr;
  408. X    } else {
  409. X        for (prevPtr = varPtr->searchPtr; ; prevPtr = prevPtr->nextPtr) {
  410. X        if (prevPtr->nextPtr == searchPtr) {
  411. X            prevPtr->nextPtr = searchPtr->nextPtr;
  412. X            break;
  413. X        }
  414. X        }
  415. X    }
  416. X    ckfree((char *) searchPtr);
  417. X    } else if ((c == 'n') && (strncmp(argv[1], "names", length) == 0)
  418. X        && (length >= 2)) {
  419. X    Tcl_HashSearch search;
  420. X    Var *varPtr2;
  421. X
  422. X    if (argc != 3) {
  423. X        Tcl_AppendResult(interp, "wrong # args: should be \"",
  424. X            argv[0], " names arrayName\"", (char *) NULL);
  425. X        return TCL_ERROR;
  426. X    }
  427. X    for (hPtr = Tcl_FirstHashEntry(varPtr->value.tablePtr, &search);
  428. X        hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  429. X        varPtr2 = (Var *) Tcl_GetHashValue(hPtr);
  430. X        if (varPtr2->flags & VAR_UNDEFINED) {
  431. X        continue;
  432. X        }
  433. X        Tcl_AppendElement(interp,
  434. X            Tcl_GetHashKey(varPtr->value.tablePtr, hPtr), 0);
  435. X    }
  436. X    } else if ((c == 'n') && (strncmp(argv[1], "nextelement", length) == 0)
  437. X        && (length >= 2)) {
  438. X    ArraySearch *searchPtr;
  439. X    Tcl_HashEntry *hPtr;
  440. X
  441. X    if (argc != 4) {
  442. X        Tcl_AppendResult(interp, "wrong # args: should be \"",
  443. X            argv[0], " nextelement arrayName searchId\"",
  444. X            (char *) NULL);
  445. X        return TCL_ERROR;
  446. X    }
  447. X    searchPtr = ParseSearchId(interp, varPtr, argv[2], argv[3]);
  448. X    if (searchPtr == NULL) {
  449. X        return TCL_ERROR;
  450. X    }
  451. X    while (1) {
  452. X        Var *varPtr2;
  453. X
  454. X        hPtr = searchPtr->nextEntry;
  455. X        if (hPtr == NULL) {
  456. X        hPtr = Tcl_NextHashEntry(&searchPtr->search);
  457. X        if (hPtr == NULL) {
  458. X            return TCL_OK;
  459. X        }
  460. X        } else {
  461. X        searchPtr->nextEntry = NULL;
  462. X        }
  463. X        varPtr2 = (Var *) Tcl_GetHashValue(hPtr);
  464. X        if (!(varPtr2->flags & VAR_UNDEFINED)) {
  465. X        break;
  466. X        }
  467. X    }
  468. X    interp->result = Tcl_GetHashKey(varPtr->value.tablePtr, hPtr);
  469. X    } else if ((c == 's') && (strncmp(argv[1], "size", length) == 0)
  470. X        && (length >= 2)) {
  471. X    Tcl_HashSearch search;
  472. X    Var *varPtr2;
  473. X    int size;
  474. X
  475. X    if (argc != 3) {
  476. X        Tcl_AppendResult(interp, "wrong # args: should be \"",
  477. X            argv[0], " size arrayName\"", (char *) NULL);
  478. X        return TCL_ERROR;
  479. X    }
  480. X    size = 0;
  481. X    for (hPtr = Tcl_FirstHashEntry(varPtr->value.tablePtr, &search);
  482. X        hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  483. X        varPtr2 = (Var *) Tcl_GetHashValue(hPtr);
  484. X        if (varPtr2->flags & VAR_UNDEFINED) {
  485. X        continue;
  486. X        }
  487. X        size++;
  488. X    }
  489. X    sprintf(interp->result, "%d", size);
  490. X    } else if ((c == 's') && (strncmp(argv[1], "startsearch", length) == 0)
  491. X        && (length >= 2)) {
  492. X    ArraySearch *searchPtr;
  493. X
  494. X    if (argc != 3) {
  495. X        Tcl_AppendResult(interp, "wrong # args: should be \"",
  496. X            argv[0], " startsearch arrayName\"", (char *) NULL);
  497. X        return TCL_ERROR;
  498. X    }
  499. X    searchPtr = (ArraySearch *) ckalloc(sizeof(ArraySearch));
  500. X    if (varPtr->searchPtr == NULL) {
  501. X        searchPtr->id = 1;
  502. X        Tcl_AppendResult(interp, "s-1-", argv[2], (char *) NULL);
  503. X    } else {
  504. X        char string[20];
  505. X
  506. X        searchPtr->id = varPtr->searchPtr->id + 1;
  507. X        sprintf(string, "%d", searchPtr->id);
  508. X        Tcl_AppendResult(interp, "s-", string, "-", argv[2],
  509. X            (char *) NULL);
  510. X    }
  511. X    searchPtr->varPtr = varPtr;
  512. X    searchPtr->nextEntry = Tcl_FirstHashEntry(varPtr->value.tablePtr,
  513. X        &searchPtr->search);
  514. X    searchPtr->nextPtr = varPtr->searchPtr;
  515. X    varPtr->searchPtr = searchPtr;
  516. X    } else {
  517. X    Tcl_AppendResult(interp, "bad option \"", argv[1],
  518. X        "\": should be anymore, donesearch, names, nextelement, ",
  519. X        "size, or startsearch", (char *) NULL);
  520. X    return TCL_ERROR;
  521. X    }
  522. X    return TCL_OK;
  523. X}
  524. X
  525. X/*
  526. X *----------------------------------------------------------------------
  527. X *
  528. X * Tcl_GlobalCmd --
  529. X *
  530. X *    This procedure is invoked to process the "global" Tcl command.
  531. X *    See the user documentation for details on what it does.
  532. X *
  533. X * Results:
  534. X *    A standard Tcl result value.
  535. X *
  536. X * Side effects:
  537. X *    See the user documentation.
  538. X *
  539. X *----------------------------------------------------------------------
  540. X */
  541. X
  542. X    /* ARGSUSED */
  543. Xint
  544. XTcl_GlobalCmd(dummy, interp, argc, argv)
  545. X    ClientData dummy;            /* Not used. */
  546. X    Tcl_Interp *interp;            /* Current interpreter. */
  547. X    int argc;                /* Number of arguments. */
  548. X    char **argv;            /* Argument strings. */
  549. X{
  550. X    Var *varPtr, *gVarPtr;
  551. X    register Interp *iPtr = (Interp *) interp;
  552. X    Tcl_HashEntry *hPtr, *hPtr2;
  553. X    int new;
  554. X
  555. X    if (argc < 2) {
  556. X    Tcl_AppendResult((Tcl_Interp *) iPtr, "wrong # args: should be \"",
  557. X        argv[0], " varName ?varName ...?\"", (char *) NULL);
  558. X    return TCL_ERROR;
  559. X    }
  560. X    if (iPtr->varFramePtr == NULL) {
  561. X    return TCL_OK;
  562. X    }
  563. X
  564. X    for (argc--, argv++; argc > 0; argc--, argv++) {
  565. X    hPtr = Tcl_CreateHashEntry(&iPtr->globalTable, *argv, &new);
  566. X    if (new) {
  567. X        gVarPtr = NewVar(0);
  568. X        gVarPtr->flags |= VAR_UNDEFINED;
  569. X        Tcl_SetHashValue(hPtr, gVarPtr);
  570. X    } else {
  571. X        gVarPtr = (Var *) Tcl_GetHashValue(hPtr);
  572. X    }
  573. X    hPtr2 = Tcl_CreateHashEntry(&iPtr->varFramePtr->varTable, *argv, &new);
  574. X    if (!new) {
  575. X        Var *varPtr;
  576. X        varPtr = (Var *) Tcl_GetHashValue(hPtr2);
  577. X        if (varPtr->flags & VAR_UPVAR) {
  578. X        continue;
  579. X        } else {
  580. X        Tcl_AppendResult((Tcl_Interp *) iPtr, "variable \"", *argv,
  581. X            "\" already exists", (char *) NULL);
  582. X        return TCL_ERROR;
  583. X        }
  584. X    }
  585. X    varPtr = NewVar(0);
  586. X    varPtr->flags |= VAR_UPVAR;
  587. X    varPtr->value.upvarPtr = hPtr;
  588. X    gVarPtr->upvarUses++;
  589. X    Tcl_SetHashValue(hPtr2, varPtr);
  590. X    }
  591. X    return TCL_OK;
  592. X}
  593. X
  594. X/*
  595. X *----------------------------------------------------------------------
  596. X *
  597. X * Tcl_UpvarCmd --
  598. X *
  599. X *    This procedure is invoked to process the "upvar" Tcl command.
  600. X *    See the user documentation for details on what it does.
  601. X *
  602. X * Results:
  603. X *    A standard Tcl result value.
  604. X *
  605. X * Side effects:
  606. X *    See the user documentation.
  607. X *
  608. X *----------------------------------------------------------------------
  609. X */
  610. X
  611. X    /* ARGSUSED */
  612. Xint
  613. XTcl_UpvarCmd(dummy, interp, argc, argv)
  614. X    ClientData dummy;            /* Not used. */
  615. X    Tcl_Interp *interp;            /* Current interpreter. */
  616. X    int argc;                /* Number of arguments. */
  617. X    char **argv;            /* Argument strings. */
  618. X{
  619. X    register Interp *iPtr = (Interp *) interp;
  620. X    int result;
  621. X    CallFrame *framePtr;
  622. X    Var *varPtr = NULL;
  623. X    Tcl_HashTable *upVarTablePtr;
  624. X    Tcl_HashEntry *hPtr, *hPtr2;
  625. X    int new;
  626. X    Var *upVarPtr;
  627. X
  628. X    if (argc < 3) {
  629. X    upvarSyntax:
  630. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  631. X        " ?level? otherVar localVar ?otherVar localVar ...?\"",
  632. X        (char *) NULL);
  633. X    return TCL_ERROR;
  634. X    }
  635. X
  636. X    /*
  637. X     * Find the hash table containing the variable being referenced.
  638. X     */
  639. X
  640. X    result = TclGetFrame(interp, argv[1], &framePtr);
  641. X    if (result == -1) {
  642. X    return TCL_ERROR;
  643. X    }
  644. X    argc -= result+1;
  645. X    argv += result+1;
  646. X    if (framePtr == NULL) {
  647. X    upVarTablePtr = &iPtr->globalTable;
  648. X    } else {
  649. X    upVarTablePtr = &framePtr->varTable;
  650. X    }
  651. X
  652. X    if ((argc & 1) != 0) {
  653. X    goto upvarSyntax;
  654. X    }
  655. X
  656. X    /*
  657. X     * Iterate over all the pairs of (local variable, other variable)
  658. X     * names.  For each pair, create a hash table entry in the upper
  659. X     * context (if the name wasn't there already), then associate it
  660. X     * with a new local variable.
  661. X     */
  662. X
  663. X    while (argc > 0) {
  664. X        hPtr = Tcl_CreateHashEntry(upVarTablePtr, argv[0], &new);
  665. X        if (new) {
  666. X            upVarPtr = NewVar(0);
  667. X            upVarPtr->flags |= VAR_UNDEFINED;
  668. X            Tcl_SetHashValue(hPtr, upVarPtr);
  669. X        } else {
  670. X            upVarPtr = (Var *) Tcl_GetHashValue(hPtr);
  671. X        if (upVarPtr->flags & VAR_UPVAR) {
  672. X        hPtr = upVarPtr->value.upvarPtr;
  673. X        upVarPtr = (Var *) Tcl_GetHashValue(hPtr);
  674. X        }
  675. X        }
  676. X
  677. X        hPtr2 = Tcl_CreateHashEntry(&iPtr->varFramePtr->varTable,
  678. X                    argv[1], &new);
  679. X        if (!new) {
  680. X            Tcl_AppendResult((Tcl_Interp *) iPtr, "variable \"", argv[1],
  681. X                "\" already exists", (char *) NULL);
  682. X            return TCL_ERROR;
  683. X        }
  684. X        varPtr = NewVar(0);
  685. X        varPtr->flags |= VAR_UPVAR;
  686. X        varPtr->value.upvarPtr = hPtr;
  687. X        upVarPtr->upvarUses++;
  688. X        Tcl_SetHashValue(hPtr2, varPtr);
  689. X
  690. X        argc -= 2;
  691. X        argv += 2;
  692. X    }
  693. X    return TCL_OK;
  694. X}
  695. X
  696. X/*
  697. X *----------------------------------------------------------------------
  698. X *
  699. X * TclDeleteVars --
  700. X *
  701. X *    This procedure is called to recycle all the storage space
  702. X *    associated with a table of variables.  For this procedure
  703. X *    to work correctly, it must not be possible for any of the
  704. X *    variable in the table to be accessed from Tcl commands
  705. X *    (e.g. from trace procedures).
  706. X *
  707. X * Results:
  708. X *    None.
  709. X *
  710. X * Side effects:
  711. X *    Variables are deleted and trace procedures are invoked, if
  712. X *    any are declared.
  713. X *
  714. X *----------------------------------------------------------------------
  715. X */
  716. X
  717. Xvoid
  718. XTclDeleteVars(iPtr, tablePtr)
  719. X    Interp *iPtr;        /* Interpreter to which variables belong. */
  720. X    Tcl_HashTable *tablePtr;    /* Hash table containing variables to
  721. X                 * delete. */
  722. X{
  723. X    Tcl_HashSearch search;
  724. X    Tcl_HashEntry *hPtr;
  725. X    register Var *varPtr;
  726. X    int flags, globalFlag;
  727. X
  728. X    flags = TCL_TRACE_UNSETS;
  729. X    if (tablePtr == &iPtr->globalTable) {
  730. X    flags |= TCL_INTERP_DESTROYED | TCL_GLOBAL_ONLY;
  731. X    }
  732. X    for (hPtr = Tcl_FirstHashEntry(tablePtr, &search); hPtr != NULL;
  733. X        hPtr = Tcl_NextHashEntry(&search)) {
  734. X    varPtr = (Var *) Tcl_GetHashValue(hPtr);
  735. X
  736. X    /*
  737. X     * For global/upvar variables referenced in procedures, free up the
  738. X     * local space and then decrement the reference count on the
  739. X     * variable referred to.  If there are no more references to the
  740. X     * global/upvar and it is undefined and has no traces set, then
  741. X     * follow on and delete the referenced variable too.
  742. X     */
  743. X
  744. X    globalFlag = 0;
  745. X    if (varPtr->flags & VAR_UPVAR) {
  746. X        hPtr = varPtr->value.upvarPtr;
  747. X        ckfree((char *) varPtr);
  748. X        varPtr = (Var *) Tcl_GetHashValue(hPtr);
  749. X        varPtr->upvarUses--;
  750. X        if ((varPtr->upvarUses != 0) || !(varPtr->flags & VAR_UNDEFINED)
  751. X            || (varPtr->tracePtr != NULL)) {
  752. X        continue;
  753. X        }
  754. X        globalFlag = TCL_GLOBAL_ONLY;
  755. X    }
  756. X
  757. X    /*
  758. X     * Invoke traces on the variable that is being deleted, then
  759. X     * free up the variable's space (no need to free the hash entry
  760. X     * here, unless we're dealing with a global variable:  the
  761. X     * hash entries will be deleted automatically when the whole
  762. X     * table is deleted).
  763. X     */
  764. X
  765. X    if (varPtr->tracePtr != NULL) {
  766. X        (void) CallTraces(iPtr, (Var *) NULL, hPtr,
  767. X            Tcl_GetHashKey(tablePtr, hPtr), (char *) NULL,
  768. X            flags | globalFlag);
  769. X        while (varPtr->tracePtr != NULL) {
  770. X        VarTrace *tracePtr = varPtr->tracePtr;
  771. X        varPtr->tracePtr = tracePtr->nextPtr;
  772. X        ckfree((char *) tracePtr);
  773. X        }
  774. X    }
  775. X    if (varPtr->flags & VAR_ARRAY) {
  776. X        DeleteArray(iPtr, Tcl_GetHashKey(tablePtr, hPtr), varPtr,
  777. X            flags | globalFlag);
  778. X    }
  779. X    if (globalFlag) {
  780. X        Tcl_DeleteHashEntry(hPtr);
  781. X    }
  782. X    ckfree((char *) varPtr);
  783. X    }
  784. X    Tcl_DeleteHashTable(tablePtr);
  785. X}
  786. X
  787. X/*
  788. X *----------------------------------------------------------------------
  789. X *
  790. X * CallTraces --
  791. X *
  792. X *    This procedure is invoked to find and invoke relevant
  793. X *    trace procedures associated with a particular operation on
  794. X *    a variable.  This procedure invokes traces both on the
  795. X *    variable and on its containing array (where relevant).
  796. X *
  797. X * Results:
  798. X *    The return value is NULL if no trace procedures were invoked, or
  799. X *    if all the invoked trace procedures returned successfully.
  800. X *    The return value is non-zero if a trace procedure returned an
  801. X *    error (in this case no more trace procedures were invoked after
  802. X *    the error was returned).  In this case the return value is a
  803. X *    pointer to a static string describing the error.
  804. X *
  805. X * Side effects:
  806. X *    Almost anything can happen, depending on trace;  this procedure
  807. X *    itself doesn't have any side effects.
  808. X *
  809. X *----------------------------------------------------------------------
  810. X */
  811. X
  812. Xstatic char *
  813. XCallTraces(iPtr, arrayPtr, hPtr, name1, name2, flags)
  814. X    Interp *iPtr;            /* Interpreter containing variable. */
  815. X    register Var *arrayPtr;        /* Pointer to array variable that
  816. X                     * contains the variable, or NULL if
  817. X                     * the variable isn't an element of an
  818. X                     * array. */
  819. X    Tcl_HashEntry *hPtr;        /* Hash table entry corresponding to
  820. X                     * variable whose traces are to be
  821. X                     * invoked. */
  822. X    char *name1, *name2;        /* Variable's two-part name. */
  823. X    int flags;                /* Flags to pass to trace procedures:
  824. X                     * indicates what's happening to
  825. X                     * variable, plus other stuff like
  826. X                     * TCL_GLOBAL_ONLY and
  827. X                     * TCL_INTERP_DESTROYED. */
  828. X{
  829. X    Var *varPtr;
  830. X    register VarTrace *tracePtr;
  831. X    ActiveVarTrace active;
  832. X    char *result;
  833. X    int savedArrayFlags = 0;        /* (Initialization not needed except
  834. X                     * to prevent compiler warning) */
  835. X
  836. X    /*
  837. X     * If there are already similar trace procedures active for the
  838. X     * variable, don't call them again.
  839. X     */
  840. X
  841. X    varPtr = (Var *) Tcl_GetHashValue(hPtr);
  842. X    if (varPtr->flags & VAR_TRACE_ACTIVE) {
  843. X    return NULL;
  844. X    }
  845. X    varPtr->flags |= VAR_TRACE_ACTIVE;
  846. X
  847. X    /*
  848. X     * Invoke traces on the array containing the variable, if relevant.
  849. X     */
  850. X
  851. X    result = NULL;
  852. X    active.nextPtr = iPtr->activeTracePtr;
  853. X    iPtr->activeTracePtr = &active;
  854. X    if (arrayPtr != NULL) {
  855. X    savedArrayFlags = arrayPtr->flags;
  856. X    arrayPtr->flags |= VAR_ELEMENT_ACTIVE;
  857. X    for (tracePtr = arrayPtr->tracePtr;  tracePtr != NULL;
  858. X        tracePtr = active.nextTracePtr) {
  859. X        active.nextTracePtr = tracePtr->nextPtr;
  860. X        if (!(tracePtr->flags & flags)) {
  861. X        continue;
  862. X        }
  863. X        result = (*tracePtr->traceProc)(tracePtr->clientData,
  864. X            (Tcl_Interp *) iPtr, name1, name2, flags);
  865. X        if (result != NULL) {
  866. X        if (flags & TCL_TRACE_UNSETS) {
  867. X            result = NULL;
  868. X        } else {
  869. X            goto done;
  870. X        }
  871. X        }
  872. X    }
  873. X    }
  874. X
  875. X    /*
  876. X     * Invoke traces on the variable itself.
  877. X     */
  878. X
  879. X    if (flags & TCL_TRACE_UNSETS) {
  880. X    flags |= TCL_TRACE_DESTROYED;
  881. X    }
  882. X    for (tracePtr = varPtr->tracePtr; tracePtr != NULL;
  883. X        tracePtr = active.nextTracePtr) {
  884. X    active.nextTracePtr = tracePtr->nextPtr;
  885. X    if (!(tracePtr->flags & flags)) {
  886. X        continue;
  887. X    }
  888. X    result = (*tracePtr->traceProc)(tracePtr->clientData,
  889. X        (Tcl_Interp *) iPtr, name1, name2, flags);
  890. X    if (result != NULL) {
  891. X        if (flags & TCL_TRACE_UNSETS) {
  892. X        result = NULL;
  893. X        } else {
  894. X        goto done;
  895. X        }
  896. X    }
  897. X    }
  898. X
  899. X    /*
  900. X     * Restore the variable's flags, remove the record of our active
  901. X     * traces, and then return.  Remember that the variable could have
  902. X     * been re-allocated during the traces, but its hash entry won't
  903. X     * change.
  904. X     */
  905. X
  906. X    done:
  907. X    if (arrayPtr != NULL) {
  908. X    arrayPtr->flags = savedArrayFlags;
  909. X    }
  910. X    varPtr = (Var *) Tcl_GetHashValue(hPtr);
  911. X    varPtr->flags &= ~VAR_TRACE_ACTIVE;
  912. X    iPtr->activeTracePtr = active.nextPtr;
  913. X    return result;
  914. X}
  915. X
  916. X/*
  917. X *----------------------------------------------------------------------
  918. X *
  919. X * NewVar --
  920. X *
  921. X *    Create a new variable with a given initial value.
  922. X *
  923. X * Results:
  924. X *    The return value is a pointer to the new variable structure.
  925. X *    The variable will not be part of any hash table yet, and its
  926. X *    upvarUses count is initialized to 0.  Its initial value will
  927. X *    be empty, but "space" bytes will be available in the value
  928. X *    area.
  929. X *
  930. X * Side effects:
  931. X *    Storage gets allocated.
  932. X *
  933. X *----------------------------------------------------------------------
  934. X */
  935. X
  936. Xstatic Var *
  937. XNewVar(space)
  938. X    int space;        /* Minimum amount of space to allocate
  939. X             * for variable's value. */
  940. X{
  941. X    int extra;
  942. X    register Var *varPtr;
  943. X
  944. X    extra = space - sizeof(varPtr->value);
  945. X    if (extra < 0) {
  946. X    extra = 0;
  947. X    space = sizeof(varPtr->value);
  948. X    }
  949. X    varPtr = (Var *) ckalloc((unsigned) (sizeof(Var) + extra));
  950. X    varPtr->valueLength = 0;
  951. X    varPtr->valueSpace = space;
  952. X    varPtr->upvarUses = 0;
  953. X    varPtr->tracePtr = NULL;
  954. X    varPtr->searchPtr = NULL;
  955. X    varPtr->flags = 0;
  956. X    varPtr->value.string[0] = 0;
  957. X    return varPtr;
  958. X}
  959. X
  960. X/*
  961. X *----------------------------------------------------------------------
  962. X *
  963. X * ParseSearchId --
  964. X *
  965. X *    This procedure translates from a string to a pointer to an
  966. X *    active array search (if there is one that matches the string).
  967. X *
  968. X * Results:
  969. X *    The return value is a pointer to the array search indicated
  970. X *    by string, or NULL if there isn't one.  If NULL is returned,
  971. X *    interp->result contains an error message.
  972. X *
  973. X * Side effects:
  974. X *    None.
  975. X *
  976. X *----------------------------------------------------------------------
  977. X */
  978. X
  979. Xstatic ArraySearch *
  980. XParseSearchId(interp, varPtr, varName, string)
  981. X    Tcl_Interp *interp;        /* Interpreter containing variable. */
  982. X    Var *varPtr;        /* Array variable search is for. */
  983. X    char *varName;        /* Name of array variable that search is
  984. X                 * supposed to be for. */
  985. X    char *string;        /* String containing id of search.  Must have
  986. X                 * form "search-num-var" where "num" is a
  987. X                 * decimal number and "var" is a variable
  988. X                 * name. */
  989. X{
  990. X    char *end;
  991. X    int id;
  992. X    ArraySearch *searchPtr;
  993. X
  994. X    /*
  995. X     * Parse the id into the three parts separated by dashes.
  996. X     */
  997. X
  998. X    if ((string[0] != 's') || (string[1] != '-')) {
  999. X    syntax:
  1000. X    Tcl_AppendResult(interp, "illegal search identifier \"", string,
  1001. X        "\"", (char *) NULL);
  1002. X    return NULL;
  1003. X    }
  1004. X    id = strtoul(string+2, &end, 10);
  1005. X    if ((end == (string+2)) || (*end != '-')) {
  1006. X    goto syntax;
  1007. X    }
  1008. X    if (strcmp(end+1, varName) != 0) {
  1009. X    Tcl_AppendResult(interp, "search identifier \"", string,
  1010. X        "\" isn't for variable \"", varName, "\"", (char *) NULL);
  1011. X    return NULL;
  1012. X    }
  1013. X
  1014. X    /*
  1015. X     * Search through the list of active searches on the interpreter
  1016. X     * to see if the desired one exists.
  1017. X     */
  1018. X
  1019. X    for (searchPtr = varPtr->searchPtr; searchPtr != NULL;
  1020. X        searchPtr = searchPtr->nextPtr) {
  1021. X    if (searchPtr->id == id) {
  1022. X        return searchPtr;
  1023. X    }
  1024. X    }
  1025. X    Tcl_AppendResult(interp, "couldn't find search \"", string, "\"",
  1026. X        (char *) NULL);
  1027. X    return NULL;
  1028. X}
  1029. X
  1030. X/*
  1031. X *----------------------------------------------------------------------
  1032. X *
  1033. X * DeleteSearches --
  1034. X *
  1035. X *    This procedure is called to free up all of the searches
  1036. X *    associated with an array variable.
  1037. X *
  1038. X * Results:
  1039. X *    None.
  1040. X *
  1041. X * Side effects:
  1042. X *    Memory is released to the storage allocator.
  1043. X *
  1044. X *----------------------------------------------------------------------
  1045. X */
  1046. X
  1047. Xstatic void
  1048. XDeleteSearches(arrayVarPtr)
  1049. X    register Var *arrayVarPtr;        /* Variable whose searches are
  1050. X                     * to be deleted. */
  1051. X{
  1052. X    ArraySearch *searchPtr;
  1053. X
  1054. X    while (arrayVarPtr->searchPtr != NULL) {
  1055. X    searchPtr = arrayVarPtr->searchPtr;
  1056. X    arrayVarPtr->searchPtr = searchPtr->nextPtr;
  1057. X    ckfree((char *) searchPtr);
  1058. X    }
  1059. X}
  1060. X
  1061. X/*
  1062. X *----------------------------------------------------------------------
  1063. X *
  1064. X * DeleteArray --
  1065. X *
  1066. X *    This procedure is called to free up everything in an array
  1067. X *    variable.  It's the caller's responsibility to make sure
  1068. X *    that the array is no longer accessible before this procedure
  1069. X *    is called.
  1070. X *
  1071. X * Results:
  1072. X *    None.
  1073. X *
  1074. X * Side effects:
  1075. X *    All storage associated with varPtr's array elements is deleted
  1076. X *    (including the hash table).  Any delete trace procedures for
  1077. X *    array elements are invoked.
  1078. X *
  1079. X *----------------------------------------------------------------------
  1080. X */
  1081. X
  1082. Xstatic void
  1083. XDeleteArray(iPtr, arrayName, varPtr, flags)
  1084. X    Interp *iPtr;            /* Interpreter containing array. */
  1085. X    char *arrayName;            /* Name of array (used for trace
  1086. X                     * callbacks). */
  1087. X    Var *varPtr;            /* Pointer to variable structure. */
  1088. X    int flags;                /* Flags to pass to CallTraces:
  1089. X                     * TCL_TRACE_UNSETS and sometimes
  1090. X                     * TCL_INTERP_DESTROYED and/or
  1091. X                     * TCL_GLOBAL_ONLY. */
  1092. X{
  1093. X    Tcl_HashSearch search;
  1094. X    register Tcl_HashEntry *hPtr;
  1095. X    register Var *elPtr;
  1096. X
  1097. X    DeleteSearches(varPtr);
  1098. X    for (hPtr = Tcl_FirstHashEntry(varPtr->value.tablePtr, &search);
  1099. X        hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  1100. X    elPtr = (Var *) Tcl_GetHashValue(hPtr);
  1101. X    if (elPtr->tracePtr != NULL) {
  1102. X        (void) CallTraces(iPtr, (Var *) NULL, hPtr, arrayName,
  1103. X            Tcl_GetHashKey(varPtr->value.tablePtr, hPtr), flags);
  1104. X        while (elPtr->tracePtr != NULL) {
  1105. X        VarTrace *tracePtr = elPtr->tracePtr;
  1106. X        elPtr->tracePtr = tracePtr->nextPtr;
  1107. X        ckfree((char *) tracePtr);
  1108. X        }
  1109. X    }
  1110. X    if (elPtr->flags & VAR_SEARCHES_POSSIBLE) {
  1111. X        panic("DeleteArray found searches on array alement!");
  1112. X    }
  1113. X    ckfree((char *) elPtr);
  1114. X    }
  1115. X    Tcl_DeleteHashTable(varPtr->value.tablePtr);
  1116. X    ckfree((char *) varPtr->value.tablePtr);
  1117. X}
  1118. X
  1119. X/*
  1120. X *----------------------------------------------------------------------
  1121. X *
  1122. X * VarErrMsg --
  1123. X *
  1124. X *    Generate a reasonable error message describing why a variable
  1125. X *    operation failed.
  1126. X *
  1127. X * Results:
  1128. X *    None.
  1129. X *
  1130. X * Side effects:
  1131. X *    Interp->result is reset to hold a message identifying the
  1132. X *    variable given by name1 and name2 and describing why the
  1133. X *    variable operation failed.
  1134. X *
  1135. X *----------------------------------------------------------------------
  1136. X */
  1137. X
  1138. Xstatic void
  1139. XVarErrMsg(interp, name1, name2, operation, reason)
  1140. X    Tcl_Interp *interp;        /* Interpreter in which to record message. */
  1141. X    char *name1, *name2;    /* Variable's two-part name. */
  1142. X    char *operation;        /* String describing operation that failed,
  1143. X                 * e.g. "read", "set", or "unset". */
  1144. X    char *reason;        /* String describing why operation failed. */
  1145. X{
  1146. X    Tcl_ResetResult(interp);
  1147. X    Tcl_AppendResult(interp, "can't ", operation, " \"", name1, (char *) NULL);
  1148. X    if (name2 != NULL) {
  1149. X    Tcl_AppendResult(interp, "(", name2, ")", (char *) NULL);
  1150. X    }
  1151. X    Tcl_AppendResult(interp, "\": ", reason, (char *) NULL);
  1152. X}
  1153. END_OF_FILE
  1154. if test 30825 -ne `wc -c <'tcl6.1/tclVar.c.2'`; then
  1155.     echo shar: \"'tcl6.1/tclVar.c.2'\" unpacked with wrong size!
  1156. fi
  1157. # end of 'tcl6.1/tclVar.c.2'
  1158. fi
  1159. echo shar: End of archive 22 \(of 33\).
  1160. cp /dev/null ark22isdone
  1161. MISSING=""
  1162. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ; do
  1163.     if test ! -f ark${I}isdone ; then
  1164.     MISSING="${MISSING} ${I}"
  1165.     fi
  1166. done
  1167. if test "${MISSING}" = "" ; then
  1168.     echo You have unpacked all 33 archives.
  1169.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1170. else
  1171.     echo You still need to unpack the following archives:
  1172.     echo "        " ${MISSING}
  1173. fi
  1174. ##  End of shell archive.
  1175. exit 0
  1176.  
  1177. exit 0 # Just in case...
  1178. -- 
  1179. Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
  1180. Sterling Software, IMD           UUCP:     uunet!sparky!kent
  1181. Phone:    (402) 291-8300         FAX:      (402) 291-4362
  1182. Please send comp.sources.misc-related mail to kent@uunet.uu.net.
  1183.