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

  1. Newsgroups: comp.sources.misc
  2. From: karl@sugar.neosoft.com (Karl Lehenbauer)
  3. Subject:  v25i084:  tcl - tool command language, version 6.1, Part16/33
  4. Message-ID: <1991Nov15.224724.20718@sparky.imd.sterling.com>
  5. X-Md4-Signature: 32ec3174c0763e68e8e984b8f3ee3a7c
  6. Date: Fri, 15 Nov 1991 22:47:24 GMT
  7. Approved: kent@sparky.imd.sterling.com
  8.  
  9. Submitted-by: karl@sugar.neosoft.com (Karl Lehenbauer)
  10. Posting-number: Volume 25, Issue 84
  11. Archive-name: tcl/part16
  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 16 (of 33)."
  21. # Contents:  tcl6.1/tclCmdAH.c tcl6.1/tclHash.c
  22. # Wrapped by karl@one on Tue Nov 12 19:44:24 1991
  23. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  24. if test -f 'tcl6.1/tclCmdAH.c' -a "${1}" != "-c" ; then 
  25.   echo shar: Will not clobber existing file \"'tcl6.1/tclCmdAH.c'\"
  26. else
  27. echo shar: Extracting \"'tcl6.1/tclCmdAH.c'\" \(21838 characters\)
  28. sed "s/^X//" >'tcl6.1/tclCmdAH.c' <<'END_OF_FILE'
  29. X/* 
  30. X * tclCmdAH.c --
  31. X *
  32. X *    This file contains the top-level command routines for most of
  33. X *    the Tcl built-in commands whose names begin with the letters
  34. X *    A to H.
  35. X *
  36. X * Copyright 1987-1991 Regents of the University of California
  37. X * Permission to use, copy, modify, and distribute this
  38. X * software and its documentation for any purpose and without
  39. X * fee is hereby granted, provided that the above copyright
  40. X * notice appear in all copies.  The University of California
  41. X * makes no representations about the suitability of this
  42. X * software for any purpose.  It is provided "as is" without
  43. X * express or implied warranty.
  44. X */
  45. X
  46. X#ifndef lint
  47. Xstatic char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclCmdAH.c,v 1.73 91/11/07 09:02:11 ouster Exp $ SPRITE (Berkeley)";
  48. X#endif
  49. X
  50. X#include "tclInt.h"
  51. X
  52. X
  53. X/*
  54. X *----------------------------------------------------------------------
  55. X *
  56. X * Tcl_BreakCmd --
  57. X *
  58. X *    This procedure is invoked to process the "break" Tcl command.
  59. X *    See the user documentation for details on what it does.
  60. X *
  61. X * Results:
  62. X *    A standard Tcl result.
  63. X *
  64. X * Side effects:
  65. X *    See the user documentation.
  66. X *
  67. X *----------------------------------------------------------------------
  68. X */
  69. X
  70. X    /* ARGSUSED */
  71. Xint
  72. XTcl_BreakCmd(dummy, interp, argc, argv)
  73. X    ClientData dummy;            /* Not used. */
  74. X    Tcl_Interp *interp;            /* Current interpreter. */
  75. X    int argc;                /* Number of arguments. */
  76. X    char **argv;            /* Argument strings. */
  77. X{
  78. X    if (argc != 1) {
  79. X    Tcl_AppendResult(interp, "wrong # args: should be \"",
  80. X        argv[0], "\"", (char *) NULL);
  81. X    return TCL_ERROR;
  82. X    }
  83. X    return TCL_BREAK;
  84. X}
  85. X
  86. X/*
  87. X *----------------------------------------------------------------------
  88. X *
  89. X * Tcl_CaseCmd --
  90. X *
  91. X *    This procedure is invoked to process the "case" Tcl command.
  92. X *    See the user documentation for details on what it does.
  93. X *
  94. X * Results:
  95. X *    A standard Tcl result.
  96. X *
  97. X * Side effects:
  98. X *    See the user documentation.
  99. X *
  100. X *----------------------------------------------------------------------
  101. X */
  102. X
  103. X    /* ARGSUSED */
  104. Xint
  105. XTcl_CaseCmd(dummy, interp, argc, argv)
  106. X    ClientData dummy;            /* Not used. */
  107. X    Tcl_Interp *interp;            /* Current interpreter. */
  108. X    int argc;                /* Number of arguments. */
  109. X    char **argv;            /* Argument strings. */
  110. X{
  111. X    int i, result;
  112. X    int body;
  113. X    char *string;
  114. X    int caseArgc, splitArgs;
  115. X    char **caseArgv;
  116. X
  117. X    if (argc < 3) {
  118. X    Tcl_AppendResult(interp, "wrong # args: should be \"",
  119. X        argv[0], " string ?in? patList body ... ?default body?\"",
  120. X        (char *) NULL);
  121. X    return TCL_ERROR;
  122. X    }
  123. X    string = argv[1];
  124. X    body = -1;
  125. X    if (strcmp(argv[2], "in") == 0) {
  126. X    i = 3;
  127. X    } else {
  128. X    i = 2;
  129. X    }
  130. X    caseArgc = argc - i;
  131. X    caseArgv = argv + i;
  132. X
  133. X    /*
  134. X     * If all of the pattern/command pairs are lumped into a single
  135. X     * argument, split them out again.
  136. X     */
  137. X
  138. X    splitArgs = 0;
  139. X    if (caseArgc == 1) {
  140. X    result = Tcl_SplitList(interp, caseArgv[0], &caseArgc, &caseArgv);
  141. X    if (result != TCL_OK) {
  142. X        return result;
  143. X    }
  144. X    splitArgs = 1;
  145. X    }
  146. X
  147. X    for (i = 0; i < caseArgc; i += 2) {
  148. X    int patArgc, j;
  149. X    char **patArgv;
  150. X    register char *p;
  151. X
  152. X    if (i == (caseArgc-1)) {
  153. X        interp->result = "extra case pattern with no body";
  154. X        result = TCL_ERROR;
  155. X        goto cleanup;
  156. X    }
  157. X
  158. X    /*
  159. X     * Check for special case of single pattern (no list) with
  160. X     * no backslash sequences.
  161. X     */
  162. X
  163. X    for (p = caseArgv[i]; *p != 0; p++) {
  164. X        if (isspace(*p) || (*p == '\\')) {
  165. X        break;
  166. X        }
  167. X    }
  168. X    if (*p == 0) {
  169. X        if ((*caseArgv[i] == 'd')
  170. X            && (strcmp(caseArgv[i], "default") == 0)) {
  171. X        body = i+1;
  172. X        }
  173. X        if (Tcl_StringMatch(string, caseArgv[i])) {
  174. X        body = i+1;
  175. X        goto match;
  176. X        }
  177. X        continue;
  178. X    }
  179. X
  180. X    /*
  181. X     * Break up pattern lists, then check each of the patterns
  182. X     * in the list.
  183. X     */
  184. X
  185. X    result = Tcl_SplitList(interp, caseArgv[i], &patArgc, &patArgv);
  186. X    if (result != TCL_OK) {
  187. X        goto cleanup;
  188. X    }
  189. X    for (j = 0; j < patArgc; j++) {
  190. X        if (Tcl_StringMatch(string, patArgv[j])) {
  191. X        body = i+1;
  192. X        break;
  193. X        }
  194. X    }
  195. X    ckfree((char *) patArgv);
  196. X    if (j < patArgc) {
  197. X        break;
  198. X    }
  199. X    }
  200. X
  201. X    match:
  202. X    if (body != -1) {
  203. X    result = Tcl_Eval(interp, caseArgv[body], 0, (char **) NULL);
  204. X    if (result == TCL_ERROR) {
  205. X        char msg[100];
  206. X        sprintf(msg, "\n    (\"%.50s\" arm line %d)", caseArgv[i],
  207. X            interp->errorLine);
  208. X        Tcl_AddErrorInfo(interp, msg);
  209. X    }
  210. X    goto cleanup;
  211. X    }
  212. X
  213. X    /*
  214. X     * Nothing matched:  return nothing.
  215. X     */
  216. X
  217. X    result = TCL_OK;
  218. X
  219. X    cleanup:
  220. X    if (splitArgs) {
  221. X    ckfree((char *) caseArgv);
  222. X    }
  223. X    return result;
  224. X}
  225. X
  226. X/*
  227. X *----------------------------------------------------------------------
  228. X *
  229. X * Tcl_CatchCmd --
  230. X *
  231. X *    This procedure is invoked to process the "catch" Tcl command.
  232. X *    See the user documentation for details on what it does.
  233. X *
  234. X * Results:
  235. X *    A standard Tcl result.
  236. X *
  237. X * Side effects:
  238. X *    See the user documentation.
  239. X *
  240. X *----------------------------------------------------------------------
  241. X */
  242. X
  243. X    /* ARGSUSED */
  244. Xint
  245. XTcl_CatchCmd(dummy, interp, argc, argv)
  246. X    ClientData dummy;            /* Not used. */
  247. X    Tcl_Interp *interp;            /* Current interpreter. */
  248. X    int argc;                /* Number of arguments. */
  249. X    char **argv;            /* Argument strings. */
  250. X{
  251. X    int result;
  252. X
  253. X    if ((argc != 2) && (argc != 3)) {
  254. X    Tcl_AppendResult(interp, "wrong # args: should be \"",
  255. X        argv[0], " command ?varName?\"", (char *) NULL);
  256. X    return TCL_ERROR;
  257. X    }
  258. X    result = Tcl_Eval(interp, argv[1], 0, (char **) NULL);
  259. X    if (argc == 3) {
  260. X    if (Tcl_SetVar(interp, argv[2], interp->result, 0) == NULL) {
  261. X        Tcl_SetResult(interp, "couldn't save command result in variable",
  262. X            TCL_STATIC);
  263. X        return TCL_ERROR;
  264. X    }
  265. X    }
  266. X    Tcl_ResetResult(interp);
  267. X    sprintf(interp->result, "%d", result);
  268. X    return TCL_OK;
  269. X}
  270. X
  271. X/*
  272. X *----------------------------------------------------------------------
  273. X *
  274. X * Tcl_ConcatCmd --
  275. X *
  276. X *    This procedure is invoked to process the "concat" Tcl command.
  277. X *    See the user documentation for details on what it does.
  278. X *
  279. X * Results:
  280. X *    A standard Tcl result.
  281. X *
  282. X * Side effects:
  283. X *    See the user documentation.
  284. X *
  285. X *----------------------------------------------------------------------
  286. X */
  287. X
  288. X    /* ARGSUSED */
  289. Xint
  290. XTcl_ConcatCmd(dummy, interp, argc, argv)
  291. X    ClientData dummy;            /* Not used. */
  292. X    Tcl_Interp *interp;            /* Current interpreter. */
  293. X    int argc;                /* Number of arguments. */
  294. X    char **argv;            /* Argument strings. */
  295. X{
  296. X    if (argc == 1) {
  297. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  298. X        " arg ?arg ...?\"", (char *) NULL);
  299. X    return TCL_ERROR;
  300. X    }
  301. X
  302. X    interp->result = Tcl_Concat(argc-1, argv+1);
  303. X    interp->freeProc = (Tcl_FreeProc *) free;
  304. X    return TCL_OK;
  305. X}
  306. X
  307. X/*
  308. X *----------------------------------------------------------------------
  309. X *
  310. X * Tcl_ContinueCmd --
  311. X *
  312. X *    This procedure is invoked to process the "continue" Tcl command.
  313. X *    See the user documentation for details on what it does.
  314. X *
  315. X * Results:
  316. X *    A standard Tcl result.
  317. X *
  318. X * Side effects:
  319. X *    See the user documentation.
  320. X *
  321. X *----------------------------------------------------------------------
  322. X */
  323. X
  324. X    /* ARGSUSED */
  325. Xint
  326. XTcl_ContinueCmd(dummy, interp, argc, argv)
  327. X    ClientData dummy;            /* Not used. */
  328. X    Tcl_Interp *interp;            /* Current interpreter. */
  329. X    int argc;                /* Number of arguments. */
  330. X    char **argv;            /* Argument strings. */
  331. X{
  332. X    if (argc != 1) {
  333. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  334. X        "\"", (char *) NULL);
  335. X    return TCL_ERROR;
  336. X    }
  337. X    return TCL_CONTINUE;
  338. X}
  339. X
  340. X/*
  341. X *----------------------------------------------------------------------
  342. X *
  343. X * Tcl_ErrorCmd --
  344. X *
  345. X *    This procedure is invoked to process the "error" Tcl command.
  346. X *    See the user documentation for details on what it does.
  347. X *
  348. X * Results:
  349. X *    A standard Tcl result.
  350. X *
  351. X * Side effects:
  352. X *    See the user documentation.
  353. X *
  354. X *----------------------------------------------------------------------
  355. X */
  356. X
  357. X    /* ARGSUSED */
  358. Xint
  359. XTcl_ErrorCmd(dummy, interp, argc, argv)
  360. X    ClientData dummy;            /* Not used. */
  361. X    Tcl_Interp *interp;            /* Current interpreter. */
  362. X    int argc;                /* Number of arguments. */
  363. X    char **argv;            /* Argument strings. */
  364. X{
  365. X    Interp *iPtr = (Interp *) interp;
  366. X
  367. X    if ((argc < 2) || (argc > 4)) {
  368. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  369. X        " message ?errorInfo? ?errorCode?\"", (char *) NULL);
  370. X    return TCL_ERROR;
  371. X    }
  372. X    if ((argc >= 3) && (argv[2][0] != 0)) {
  373. X    Tcl_AddErrorInfo(interp, argv[2]);
  374. X    iPtr->flags |= ERR_ALREADY_LOGGED;
  375. X    }
  376. X    if (argc == 4) {
  377. X    Tcl_SetVar2(interp, "errorCode", (char *) NULL, argv[3],
  378. X        TCL_GLOBAL_ONLY);
  379. X    iPtr->flags |= ERROR_CODE_SET;
  380. X    }
  381. X    Tcl_SetResult(interp, argv[1], TCL_VOLATILE);
  382. X    return TCL_ERROR;
  383. X}
  384. X
  385. X/*
  386. X *----------------------------------------------------------------------
  387. X *
  388. X * Tcl_EvalCmd --
  389. X *
  390. X *    This procedure is invoked to process the "eval" Tcl command.
  391. X *    See the user documentation for details on what it does.
  392. X *
  393. X * Results:
  394. X *    A standard Tcl result.
  395. X *
  396. X * Side effects:
  397. X *    See the user documentation.
  398. X *
  399. X *----------------------------------------------------------------------
  400. X */
  401. X
  402. X    /* ARGSUSED */
  403. Xint
  404. XTcl_EvalCmd(dummy, interp, argc, argv)
  405. X    ClientData dummy;            /* Not used. */
  406. X    Tcl_Interp *interp;            /* Current interpreter. */
  407. X    int argc;                /* Number of arguments. */
  408. X    char **argv;            /* Argument strings. */
  409. X{
  410. X    int result;
  411. X    char *cmd;
  412. X
  413. X    if (argc < 2) {
  414. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  415. X        " arg ?arg ...?\"", (char *) NULL);
  416. X    return TCL_ERROR;
  417. X    }
  418. X    if (argc == 2) {
  419. X    result = Tcl_Eval(interp, argv[1], 0, (char **) NULL);
  420. X    } else {
  421. X    
  422. X    /*
  423. X     * More than one argument:  concatenate them together with spaces
  424. X     * between, then evaluate the result.
  425. X     */
  426. X    
  427. X    cmd = Tcl_Concat(argc-1, argv+1);
  428. X    result = Tcl_Eval(interp, cmd, 0, (char **) NULL);
  429. X    ckfree(cmd);
  430. X    }
  431. X    if (result == TCL_ERROR) {
  432. X    char msg[60];
  433. X    sprintf(msg, "\n    (\"eval\" body line %d)", interp->errorLine);
  434. X    Tcl_AddErrorInfo(interp, msg);
  435. X    }
  436. X    return result;
  437. X}
  438. X
  439. X/*
  440. X *----------------------------------------------------------------------
  441. X *
  442. X * Tcl_ExprCmd --
  443. X *
  444. X *    This procedure is invoked to process the "expr" Tcl command.
  445. X *    See the user documentation for details on what it does.
  446. X *
  447. X * Results:
  448. X *    A standard Tcl result.
  449. X *
  450. X * Side effects:
  451. X *    See the user documentation.
  452. X *
  453. X *----------------------------------------------------------------------
  454. X */
  455. X
  456. X    /* ARGSUSED */
  457. Xint
  458. XTcl_ExprCmd(dummy, interp, argc, argv)
  459. X    ClientData dummy;            /* Not used. */
  460. X    Tcl_Interp *interp;            /* Current interpreter. */
  461. X    int argc;                /* Number of arguments. */
  462. X    char **argv;            /* Argument strings. */
  463. X{
  464. X    if (argc != 2) {
  465. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  466. X        " expression\"", (char *) NULL);
  467. X    return TCL_ERROR;
  468. X    }
  469. X
  470. X    return Tcl_ExprString(interp, argv[1]);
  471. X}
  472. X
  473. X/*
  474. X *----------------------------------------------------------------------
  475. X *
  476. X * Tcl_ForCmd --
  477. X *
  478. X *    This procedure is invoked to process the "for" Tcl command.
  479. X *    See the user documentation for details on what it does.
  480. X *
  481. X * Results:
  482. X *    A standard Tcl result.
  483. X *
  484. X * Side effects:
  485. X *    See the user documentation.
  486. X *
  487. X *----------------------------------------------------------------------
  488. X */
  489. X
  490. X    /* ARGSUSED */
  491. Xint
  492. XTcl_ForCmd(dummy, interp, argc, argv)
  493. X    ClientData dummy;            /* Not used. */
  494. X    Tcl_Interp *interp;            /* Current interpreter. */
  495. X    int argc;                /* Number of arguments. */
  496. X    char **argv;            /* Argument strings. */
  497. X{
  498. X    int result, value;
  499. X
  500. X    if (argc != 5) {
  501. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  502. X        " start test next command\"", (char *) NULL);
  503. X    return TCL_ERROR;
  504. X    }
  505. X
  506. X    result = Tcl_Eval(interp, argv[1], 0, (char **) NULL);
  507. X    if (result != TCL_OK) {
  508. X    if (result == TCL_ERROR) {
  509. X        Tcl_AddErrorInfo(interp, "\n    (\"for\" initial command)");
  510. X    }
  511. X    return result;
  512. X    }
  513. X    while (1) {
  514. X    result = Tcl_ExprBoolean(interp, argv[2], &value);
  515. X    if (result != TCL_OK) {
  516. X        return result;
  517. X    }
  518. X    if (!value) {
  519. X        break;
  520. X    }
  521. X    result = Tcl_Eval(interp, argv[4], 0, (char **) NULL);
  522. X    if (result == TCL_CONTINUE) {
  523. X        result = TCL_OK;
  524. X    } else if (result != TCL_OK) {
  525. X        if (result == TCL_ERROR) {
  526. X        char msg[60];
  527. X        sprintf(msg, "\n    (\"for\" body line %d)", interp->errorLine);
  528. X        Tcl_AddErrorInfo(interp, msg);
  529. X        }
  530. X        break;
  531. X    }
  532. X    result = Tcl_Eval(interp, argv[3], 0, (char **) NULL);
  533. X    if (result == TCL_BREAK) {
  534. X        break;
  535. X    } else if (result != TCL_OK) {
  536. X        if (result == TCL_ERROR) {
  537. X        Tcl_AddErrorInfo(interp, "\n    (\"for\" loop-end command)");
  538. X        }
  539. X        return result;
  540. X    }
  541. X    }
  542. X    if (result == TCL_BREAK) {
  543. X    result = TCL_OK;
  544. X    }
  545. X    if (result == TCL_OK) {
  546. X    Tcl_ResetResult(interp);
  547. X    }
  548. X    return result;
  549. X}
  550. X
  551. X/*
  552. X *----------------------------------------------------------------------
  553. X *
  554. X * Tcl_ForeachCmd --
  555. X *
  556. X *    This procedure is invoked to process the "foreach" Tcl command.
  557. X *    See the user documentation for details on what it does.
  558. X *
  559. X * Results:
  560. X *    A standard Tcl result.
  561. X *
  562. X * Side effects:
  563. X *    See the user documentation.
  564. X *
  565. X *----------------------------------------------------------------------
  566. X */
  567. X
  568. X    /* ARGSUSED */
  569. Xint
  570. XTcl_ForeachCmd(dummy, interp, argc, argv)
  571. X    ClientData dummy;            /* Not used. */
  572. X    Tcl_Interp *interp;            /* Current interpreter. */
  573. X    int argc;                /* Number of arguments. */
  574. X    char **argv;            /* Argument strings. */
  575. X{
  576. X    int listArgc, i, result;
  577. X    char **listArgv;
  578. X
  579. X    if (argc != 4) {
  580. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  581. X        " varName list command\"", (char *) NULL);
  582. X    return TCL_ERROR;
  583. X    }
  584. X
  585. X    /*
  586. X     * Break the list up into elements, and execute the command once
  587. X     * for each value of the element.
  588. X     */
  589. X
  590. X    result = Tcl_SplitList(interp, argv[2], &listArgc, &listArgv);
  591. X    if (result != TCL_OK) {
  592. X    return result;
  593. X    }
  594. X    for (i = 0; i < listArgc; i++) {
  595. X    if (Tcl_SetVar(interp, argv[1], listArgv[i], 0) == NULL) {
  596. X        Tcl_SetResult(interp, "couldn't set loop variable", TCL_STATIC);
  597. X        result = TCL_ERROR;
  598. X        break;
  599. X    }
  600. X
  601. X    result = Tcl_Eval(interp, argv[3], 0, (char **) NULL);
  602. X    if (result != TCL_OK) {
  603. X        if (result == TCL_CONTINUE) {
  604. X        result = TCL_OK;
  605. X        } else if (result == TCL_BREAK) {
  606. X        result = TCL_OK;
  607. X        break;
  608. X        } else if (result == TCL_ERROR) {
  609. X        char msg[100];
  610. X        sprintf(msg, "\n    (\"foreach\" body line %d)",
  611. X            interp->errorLine);
  612. X        Tcl_AddErrorInfo(interp, msg);
  613. X        break;
  614. X        } else {
  615. X        break;
  616. X        }
  617. X    }
  618. X    }
  619. X    ckfree((char *) listArgv);
  620. X    if (result == TCL_OK) {
  621. X    Tcl_ResetResult(interp);
  622. X    }
  623. X    return result;
  624. X}
  625. X
  626. X/*
  627. X *----------------------------------------------------------------------
  628. X *
  629. X * Tcl_FormatCmd --
  630. X *
  631. X *    This procedure is invoked to process the "format" Tcl command.
  632. X *    See the user documentation for details on what it does.
  633. X *
  634. X * Results:
  635. X *    A standard Tcl result.
  636. X *
  637. X * Side effects:
  638. X *    See the user documentation.
  639. X *
  640. X *----------------------------------------------------------------------
  641. X */
  642. X
  643. X    /* ARGSUSED */
  644. Xint
  645. XTcl_FormatCmd(dummy, interp, argc, argv)
  646. X    ClientData dummy;            /* Not used. */
  647. X    Tcl_Interp *interp;            /* Current interpreter. */
  648. X    int argc;                /* Number of arguments. */
  649. X    char **argv;            /* Argument strings. */
  650. X{
  651. X    register char *format;    /* Used to read characters from the format
  652. X                 * string. */
  653. X    char newFormat[40];        /* A new format specifier is generated here. */
  654. X    int width;            /* Field width from field specifier, or 0 if
  655. X                 * no width given. */
  656. X    int precision;        /* Field precision from field specifier, or 0
  657. X                 * if no precision given. */
  658. X    int size;            /* Number of bytes needed for result of
  659. X                 * conversion, based on type of conversion
  660. X                 * ("e", "s", etc.) and width from above. */
  661. X    char *oneWordValue = NULL;    /* Used to hold value to pass to sprintf, if
  662. X                 * it's a one-word value. */
  663. X    double twoWordValue;    /* Used to hold value to pass to sprintf if
  664. X                 * it's a two-word value. */
  665. X    int useTwoWords;        /* 0 means use oneWordValue, 1 means use
  666. X                 * twoWordValue. */
  667. X    char *dst = interp->result;    /* Where result is stored.  Starts off at
  668. X                 * interp->resultSpace, but may get dynamically
  669. X                 * re-allocated if this isn't enough. */
  670. X    int dstSize = 0;        /* Number of non-null characters currently
  671. X                 * stored at dst. */
  672. X    int dstSpace = TCL_RESULT_SIZE;
  673. X                /* Total amount of storage space available
  674. X                 * in dst (not including null terminator. */
  675. X    int noPercent;        /* Special case for speed:  indicates there's
  676. X                 * no field specifier, just a string to copy. */
  677. X    char **curArg;        /* Remainder of argv array. */
  678. X
  679. X    /*
  680. X     * This procedure is a bit nasty.  The goal is to use sprintf to
  681. X     * do most of the dirty work.  There are several problems:
  682. X     * 1. this procedure can't trust its arguments.
  683. X     * 2. we must be able to provide a large enough result area to hold
  684. X     *    whatever's generated.  This is hard to estimate.
  685. X     * 2. there's no way to move the arguments from argv to the call
  686. X     *    to sprintf in a reasonable way.  This is particularly nasty
  687. X     *    because some of the arguments may be two-word values (doubles).
  688. X     * So, what happens here is to scan the format string one % group
  689. X     * at a time, making many individual calls to sprintf.
  690. X     */
  691. X
  692. X    if (argc < 2) {
  693. X    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  694. X        " formatString ?arg arg ...?\"", (char *) NULL);
  695. X    return TCL_ERROR;
  696. X    }
  697. X    curArg = argv+2;
  698. X    argc -= 2;
  699. X    for (format = argv[1]; *format != 0; ) {
  700. X    register char *newPtr = newFormat;
  701. X
  702. X    width = precision = useTwoWords = noPercent = 0;
  703. X
  704. X    /*
  705. X     * Get rid of any characters before the next field specifier.
  706. X     * Collapse backslash sequences found along the way.
  707. X     */
  708. X
  709. X    if (*format != '%') {
  710. X        register char *p;
  711. X        int bsSize;
  712. X
  713. X        oneWordValue = p = format;
  714. X        while ((*format != '%') && (*format != 0)) {
  715. X        if (*format == '\\') {
  716. X            *p = Tcl_Backslash(format, &bsSize);
  717. X            if (*p != 0) {
  718. X            p++;
  719. X            }
  720. X            format += bsSize;
  721. X        } else {
  722. X            *p = *format;
  723. X            p++;
  724. X            format++;
  725. X        }
  726. X        }
  727. X        size = p - oneWordValue;
  728. X        noPercent = 1;
  729. X        goto doField;
  730. X    }
  731. X
  732. X    if (format[1] == '%') {
  733. X        oneWordValue = format;
  734. X        size = 1;
  735. X        noPercent = 1;
  736. X        format += 2;
  737. X        goto doField;
  738. X    }
  739. X
  740. X    /*
  741. X     * Parse off a field specifier, compute how many characters
  742. X     * will be needed to store the result, and substitute for
  743. X     * "*" size specifiers.
  744. X     */
  745. X
  746. X    *newPtr = '%';
  747. X    newPtr++;
  748. X    format++;
  749. X    while ((*format == '-') || (*format == '#')) {
  750. X        *newPtr = *format;
  751. X        newPtr++;
  752. X        format++;
  753. X    }
  754. X    if (*format == '0') {
  755. X        *newPtr = '0';
  756. X        newPtr++;
  757. X        format++;
  758. X    }
  759. X    if (isdigit(*format)) {
  760. X        width = atoi(format);
  761. X        do {
  762. X        format++;
  763. X        } while (isdigit(*format));
  764. X    } else if (*format == '*') {
  765. X        if (argc <= 0) {
  766. X        goto notEnoughArgs;
  767. X        }
  768. X        if (Tcl_GetInt(interp, *curArg, &width) != TCL_OK) {
  769. X        goto fmtError;
  770. X        }
  771. X        argc--;
  772. X        curArg++;
  773. X        format++;
  774. X    }
  775. X    if (width != 0) {
  776. X        sprintf(newPtr, "%d", width);
  777. X        while (*newPtr != 0) {
  778. X        newPtr++;
  779. X        }
  780. X    }
  781. X    if (*format == '.') {
  782. X        *newPtr = '.';
  783. X        newPtr++;
  784. X        format++;
  785. X    }
  786. X    if (isdigit(*format)) {
  787. X        precision = atoi(format);
  788. X        do {
  789. X        format++;
  790. X        } while (isdigit(*format));
  791. X    } else if (*format == '*') {
  792. X        if (argc <= 0) {
  793. X        goto notEnoughArgs;
  794. X        }
  795. X        if (Tcl_GetInt(interp, *curArg, &precision) != TCL_OK) {
  796. X        goto fmtError;
  797. X        }
  798. X        argc--;
  799. X        curArg++;
  800. X        format++;
  801. X    }
  802. X    if (precision != 0) {
  803. X        sprintf(newPtr, "%d", precision);
  804. X        while (*newPtr != 0) {
  805. X        newPtr++;
  806. X        }
  807. X    }
  808. X    if (*format == 'l') {
  809. X        format++;
  810. X    }
  811. X    *newPtr = *format;
  812. X    newPtr++;
  813. X    *newPtr = 0;
  814. X    if (argc <= 0) {
  815. X        goto notEnoughArgs;
  816. X    }
  817. X    switch (*format) {
  818. X        case 'D':
  819. X        case 'O':
  820. X        case 'U':
  821. X        *newPtr = tolower(*format);
  822. X        newPtr[-1] = 'l';
  823. X        newPtr++;
  824. X        *newPtr = 0;
  825. X        case 'd':
  826. X        case 'o':
  827. X        case 'u':
  828. X        case 'x':
  829. X        case 'X':
  830. X        if (Tcl_GetInt(interp, *curArg, (int *) &oneWordValue)
  831. X            != TCL_OK) {
  832. X            goto fmtError;
  833. X        }
  834. X        size = 40;
  835. X        break;
  836. X        case 's':
  837. X        oneWordValue = *curArg;
  838. X        size = strlen(*curArg);
  839. X        break;
  840. X        case 'c':
  841. X        if (Tcl_GetInt(interp, *curArg, (int *) &oneWordValue)
  842. X            != TCL_OK) {
  843. X            goto fmtError;
  844. X        }
  845. X        size = 1;
  846. X        break;
  847. X        case 'F':
  848. X        newPtr[-1] = tolower(newPtr[-1]);
  849. X        case 'e':
  850. X        case 'E':
  851. X        case 'f':
  852. X        case 'g':
  853. X        case 'G':
  854. X        if (Tcl_GetDouble(interp, *curArg, &twoWordValue) != TCL_OK) {
  855. X            goto fmtError;
  856. X        }
  857. X        useTwoWords = 1;
  858. X        size = 320;
  859. X        if (precision > 10) {
  860. X            size += precision;
  861. X        }
  862. X        break;
  863. X        case 0:
  864. X        interp->result =
  865. X            "format string ended in middle of field specifier";
  866. X        goto fmtError;
  867. X        default:
  868. X        sprintf(interp->result, "bad field specifier \"%c\"", *format);
  869. X        goto fmtError;
  870. X    }
  871. X    argc--;
  872. X    curArg++;
  873. X    format++;
  874. X
  875. X    /*
  876. X     * Make sure that there's enough space to hold the formatted
  877. X     * result, then format it.
  878. X     */
  879. X
  880. X    doField:
  881. X    if (width > size) {
  882. X        size = width;
  883. X    }
  884. X    if ((dstSize + size) > dstSpace) {
  885. X        char *newDst;
  886. X        int newSpace;
  887. X
  888. X        newSpace = 2*(dstSize + size);
  889. X        newDst = (char *) ckalloc((unsigned) newSpace+1);
  890. X        if (dstSize != 0) {
  891. X        memcpy((VOID *) newDst, (VOID *) dst, dstSize);
  892. X        }
  893. X        if (dstSpace != TCL_RESULT_SIZE) {
  894. X        ckfree(dst);
  895. X        }
  896. X        dst = newDst;
  897. X        dstSpace = newSpace;
  898. X    }
  899. X    if (noPercent) {
  900. X        memcpy((VOID *) dst+dstSize, (VOID *) oneWordValue, size);
  901. X        dstSize += size;
  902. X        dst[dstSize] = 0;
  903. X    } else {
  904. X        if (useTwoWords) {
  905. X        sprintf(dst+dstSize, newFormat, twoWordValue);
  906. X        } else {
  907. X        sprintf(dst+dstSize, newFormat, oneWordValue);
  908. X        }
  909. X        dstSize += strlen(dst+dstSize);
  910. X    }
  911. X    }
  912. X
  913. X    interp->result = dst;
  914. X    if (dstSpace != TCL_RESULT_SIZE) {
  915. X    interp->freeProc = (Tcl_FreeProc *) free;
  916. X    } else {
  917. X    interp->freeProc = 0;
  918. X    }
  919. X    return TCL_OK;
  920. X
  921. X    notEnoughArgs:
  922. X    interp->result = "not enough arguments for all format specifiers";
  923. X    fmtError:
  924. X    if (dstSpace != TCL_RESULT_SIZE) {
  925. X    ckfree(dst);
  926. X    }
  927. X    return TCL_ERROR;
  928. X}
  929. END_OF_FILE
  930. if test 21838 -ne `wc -c <'tcl6.1/tclCmdAH.c'`; then
  931.     echo shar: \"'tcl6.1/tclCmdAH.c'\" unpacked with wrong size!
  932. fi
  933. # end of 'tcl6.1/tclCmdAH.c'
  934. fi
  935. if test -f 'tcl6.1/tclHash.c' -a "${1}" != "-c" ; then 
  936.   echo shar: Will not clobber existing file \"'tcl6.1/tclHash.c'\"
  937. else
  938. echo shar: Extracting \"'tcl6.1/tclHash.c'\" \(25018 characters\)
  939. sed "s/^X//" >'tcl6.1/tclHash.c' <<'END_OF_FILE'
  940. X/* 
  941. X * tclHash.c --
  942. X *
  943. X *    Implementation of in-memory hash tables for Tcl and Tcl-based
  944. X *    applications.
  945. X *
  946. X * Copyright 1991 Regents of the University of California
  947. X * Permission to use, copy, modify, and distribute this
  948. X * software and its documentation for any purpose and without
  949. X * fee is hereby granted, provided that this copyright
  950. X * notice appears in all copies.  The University of California
  951. X * makes no representations about the suitability of this
  952. X * software for any purpose.  It is provided "as is" without
  953. X * express or implied warranty.
  954. X */
  955. X
  956. X#ifndef lint
  957. Xstatic char rcsid[] = "$Header: /sprite/src/lib/tcl/RCS/tclHash.c,v 1.8 91/07/22 11:46:00 ouster Exp $ SPRITE (Berkeley)";
  958. X#endif /* not lint */
  959. X
  960. X#include "tclInt.h"
  961. X
  962. X/*
  963. X * Imported library procedures for which there are no header files:
  964. X */
  965. X
  966. Xextern void panic();
  967. X
  968. X/*
  969. X * When there are this many entries per bucket, on average, rebuild
  970. X * the hash table to make it larger.
  971. X */
  972. X
  973. X#define REBUILD_MULTIPLIER    3
  974. X
  975. X
  976. X/*
  977. X * The following macro takes a preliminary integer hash value and
  978. X * produces an index into a hash tables bucket list.  The idea is
  979. X * to make it so that preliminary values that are arbitrarily similar
  980. X * will end up in different buckets.  The hash function was taken
  981. X * from a random-number generator.
  982. X */
  983. X
  984. X#define RANDOM_INDEX(tablePtr, i) \
  985. X    (((((int) (i))*1103515245) >> (tablePtr)->downShift) & (tablePtr)->mask)
  986. X
  987. X/*
  988. X * Procedure prototypes for static procedures in this file:
  989. X */
  990. X
  991. Xstatic Tcl_HashEntry *    ArrayFind _ANSI_ARGS_((Tcl_HashTable *tablePtr,
  992. X                char *key));
  993. Xstatic Tcl_HashEntry *    ArrayCreate _ANSI_ARGS_((Tcl_HashTable *tablePtr,
  994. X                char *key, int *newPtr));
  995. Xstatic Tcl_HashEntry *    BogusFind _ANSI_ARGS_((Tcl_HashTable *tablePtr,
  996. X                char *key));
  997. Xstatic Tcl_HashEntry *    BogusCreate _ANSI_ARGS_((Tcl_HashTable *tablePtr,
  998. X                char *key, int *newPtr));
  999. Xstatic int        HashString _ANSI_ARGS_((char *string));
  1000. Xstatic void        RebuildTable _ANSI_ARGS_((Tcl_HashTable *tablePtr));
  1001. Xstatic Tcl_HashEntry *    StringFind _ANSI_ARGS_((Tcl_HashTable *tablePtr,
  1002. X                char *key));
  1003. Xstatic Tcl_HashEntry *    StringCreate _ANSI_ARGS_((Tcl_HashTable *tablePtr,
  1004. X                char *key, int *newPtr));
  1005. Xstatic Tcl_HashEntry *    OneWordFind _ANSI_ARGS_((Tcl_HashTable *tablePtr,
  1006. X                char *key));
  1007. Xstatic Tcl_HashEntry *    OneWordCreate _ANSI_ARGS_((Tcl_HashTable *tablePtr,
  1008. X                char *key, int *newPtr));
  1009. X
  1010. X/*
  1011. X *----------------------------------------------------------------------
  1012. X *
  1013. X * Tcl_InitHashTable --
  1014. X *
  1015. X *    Given storage for a hash table, set up the fields to prepare
  1016. X *    the hash table for use.
  1017. X *
  1018. X * Results:
  1019. X *    None.
  1020. X *
  1021. X * Side effects:
  1022. X *    TablePtr is now ready to be passed to Tcl_FindHashEntry and
  1023. X *    Tcl_CreateHashEntry.
  1024. X *
  1025. X *----------------------------------------------------------------------
  1026. X */
  1027. X
  1028. Xvoid
  1029. XTcl_InitHashTable(tablePtr, keyType)
  1030. X    register Tcl_HashTable *tablePtr;    /* Pointer to table record, which
  1031. X                     * is supplied by the caller. */
  1032. X    int keyType;            /* Type of keys to use in table:
  1033. X                     * TCL_STRING_KEYS, TCL_ONE_WORD_KEYS,
  1034. X                     * or an integer >= 2. */
  1035. X{
  1036. X    tablePtr->buckets = tablePtr->staticBuckets;
  1037. X    tablePtr->staticBuckets[0] = tablePtr->staticBuckets[1] = 0;
  1038. X    tablePtr->staticBuckets[2] = tablePtr->staticBuckets[3] = 0;
  1039. X    tablePtr->numBuckets = TCL_SMALL_HASH_TABLE;
  1040. X    tablePtr->numEntries = 0;
  1041. X    tablePtr->rebuildSize = TCL_SMALL_HASH_TABLE*REBUILD_MULTIPLIER;
  1042. X    tablePtr->downShift = 28;
  1043. X    tablePtr->mask = 3;
  1044. X    tablePtr->keyType = keyType;
  1045. X    if (keyType == TCL_STRING_KEYS) {
  1046. X    tablePtr->findProc = StringFind;
  1047. X    tablePtr->createProc = StringCreate;
  1048. X    } else if (keyType == TCL_ONE_WORD_KEYS) {
  1049. X    tablePtr->findProc = OneWordFind;
  1050. X    tablePtr->createProc = OneWordCreate;
  1051. X    } else {
  1052. X    tablePtr->findProc = ArrayFind;
  1053. X    tablePtr->createProc = ArrayCreate;
  1054. X    };
  1055. X}
  1056. X
  1057. X/*
  1058. X *----------------------------------------------------------------------
  1059. X *
  1060. X * Tcl_DeleteHashEntry --
  1061. X *
  1062. X *    Remove a single entry from a hash table.
  1063. X *
  1064. X * Results:
  1065. X *    None.
  1066. X *
  1067. X * Side effects:
  1068. X *    The entry given by entryPtr is deleted from its table and
  1069. X *    should never again be used by the caller.  It is up to the
  1070. X *    caller to free the clientData field of the entry, if that
  1071. X *    is relevant.
  1072. X *
  1073. X *----------------------------------------------------------------------
  1074. X */
  1075. X
  1076. Xvoid
  1077. XTcl_DeleteHashEntry(entryPtr)
  1078. X    Tcl_HashEntry *entryPtr;
  1079. X{
  1080. X    register Tcl_HashEntry *prevPtr;
  1081. X
  1082. X    if (*entryPtr->bucketPtr == entryPtr) {
  1083. X    *entryPtr->bucketPtr = entryPtr->nextPtr;
  1084. X    } else {
  1085. X    for (prevPtr = *entryPtr->bucketPtr; ; prevPtr = prevPtr->nextPtr) {
  1086. X        if (prevPtr == NULL) {
  1087. X        panic("malformed bucket chain in Tcl_DeleteHashEntry");
  1088. X        }
  1089. X        if (prevPtr->nextPtr == entryPtr) {
  1090. X        prevPtr->nextPtr = entryPtr->nextPtr;
  1091. X        break;
  1092. X        }
  1093. X    }
  1094. X    }
  1095. X    entryPtr->tablePtr->numEntries--;
  1096. X    ckfree((char *) entryPtr);
  1097. X}
  1098. X
  1099. X/*
  1100. X *----------------------------------------------------------------------
  1101. X *
  1102. X * Tcl_DeleteHashTable --
  1103. X *
  1104. X *    Free up everything associated with a hash table except for
  1105. X *    the record for the table itself.
  1106. X *
  1107. X * Results:
  1108. X *    None.
  1109. X *
  1110. X * Side effects:
  1111. X *    The hash table is no longer useable.
  1112. X *
  1113. X *----------------------------------------------------------------------
  1114. X */
  1115. X
  1116. Xvoid
  1117. XTcl_DeleteHashTable(tablePtr)
  1118. X    register Tcl_HashTable *tablePtr;        /* Table to delete. */
  1119. X{
  1120. X    register Tcl_HashEntry *hPtr, *nextPtr;
  1121. X    int i;
  1122. X
  1123. X    /*
  1124. X     * Free up all the entries in the table.
  1125. X     */
  1126. X
  1127. X    for (i = 0; i < tablePtr->numBuckets; i++) {
  1128. X    hPtr = tablePtr->buckets[i];
  1129. X    while (hPtr != NULL) {
  1130. X        nextPtr = hPtr->nextPtr;
  1131. X        ckfree((char *) hPtr);
  1132. X        hPtr = nextPtr;
  1133. X    }
  1134. X    }
  1135. X
  1136. X    /*
  1137. X     * Free up the bucket array, if it was dynamically allocated.
  1138. X     */
  1139. X
  1140. X    if (tablePtr->buckets != tablePtr->staticBuckets) {
  1141. X    ckfree((char *) tablePtr->buckets);
  1142. X    }
  1143. X
  1144. X    /*
  1145. X     * Arrange for panics if the table is used again without
  1146. X     * re-initialization.
  1147. X     */
  1148. X
  1149. X    tablePtr->findProc = BogusFind;
  1150. X    tablePtr->createProc = BogusCreate;
  1151. X}
  1152. X
  1153. X/*
  1154. X *----------------------------------------------------------------------
  1155. X *
  1156. X * Tcl_FirstHashEntry --
  1157. X *
  1158. X *    Locate the first entry in a hash table and set up a record
  1159. X *    that can be used to step through all the remaining entries
  1160. X *    of the table.
  1161. X *
  1162. X * Results:
  1163. X *    The return value is a pointer to the first entry in tablePtr,
  1164. X *    or NULL if tablePtr has no entries in it.  The memory at
  1165. X *    *searchPtr is initialized so that subsequent calls to
  1166. X *    Tcl_NextHashEntry will return all of the entries in the table,
  1167. X *    one at a time.
  1168. X *
  1169. X * Side effects:
  1170. X *    None.
  1171. X *
  1172. X *----------------------------------------------------------------------
  1173. X */
  1174. X
  1175. XTcl_HashEntry *
  1176. XTcl_FirstHashEntry(tablePtr, searchPtr)
  1177. X    Tcl_HashTable *tablePtr;        /* Table to search. */
  1178. X    Tcl_HashSearch *searchPtr;        /* Place to store information about
  1179. X                     * progress through the table. */
  1180. X{
  1181. X    searchPtr->tablePtr = tablePtr;
  1182. X    searchPtr->nextIndex = 0;
  1183. X    searchPtr->nextEntryPtr = NULL;
  1184. X    return Tcl_NextHashEntry(searchPtr);
  1185. X}
  1186. X
  1187. X/*
  1188. X *----------------------------------------------------------------------
  1189. X *
  1190. X * Tcl_NextHashEntry --
  1191. X *
  1192. X *    Once a hash table enumeration has been initiated by calling
  1193. X *    Tcl_FirstHashEntry, this procedure may be called to return
  1194. X *    successive elements of the table.
  1195. X *
  1196. X * Results:
  1197. X *    The return value is the next entry in the hash table being
  1198. X *    enumerated, or NULL if the end of the table is reached.
  1199. X *
  1200. X * Side effects:
  1201. X *    None.
  1202. X *
  1203. X *----------------------------------------------------------------------
  1204. X */
  1205. X
  1206. XTcl_HashEntry *
  1207. XTcl_NextHashEntry(searchPtr)
  1208. X    register Tcl_HashSearch *searchPtr;    /* Place to store information about
  1209. X                     * progress through the table.  Must
  1210. X                     * have been initialized by calling
  1211. X                     * Tcl_FirstHashEntry. */
  1212. X{
  1213. X    Tcl_HashEntry *hPtr;
  1214. X
  1215. X    while (searchPtr->nextEntryPtr == NULL) {
  1216. X    if (searchPtr->nextIndex >= searchPtr->tablePtr->numBuckets) {
  1217. X        return NULL;
  1218. X    }
  1219. X    searchPtr->nextEntryPtr =
  1220. X        searchPtr->tablePtr->buckets[searchPtr->nextIndex];
  1221. X    searchPtr->nextIndex++;
  1222. X    }
  1223. X    hPtr = searchPtr->nextEntryPtr;
  1224. X    searchPtr->nextEntryPtr = hPtr->nextPtr;
  1225. X    return hPtr;
  1226. X}
  1227. X
  1228. X/*
  1229. X *----------------------------------------------------------------------
  1230. X *
  1231. X * Tcl_HashStats --
  1232. X *
  1233. X *    Return statistics describing the layout of the hash table
  1234. X *    in its hash buckets.
  1235. X *
  1236. X * Results:
  1237. X *    The return value is a malloc-ed string containing information
  1238. X *    about tablePtr.  It is the caller's responsibility to free
  1239. X *    this string.
  1240. X *
  1241. X * Side effects:
  1242. X *    None.
  1243. X *
  1244. X *----------------------------------------------------------------------
  1245. X */
  1246. X
  1247. Xchar *
  1248. XTcl_HashStats(tablePtr)
  1249. X    Tcl_HashTable *tablePtr;        /* Table for which to produce stats. */
  1250. X{
  1251. X#define NUM_COUNTERS 10
  1252. X    int count[NUM_COUNTERS], overflow, i, j;
  1253. X    double average, tmp;
  1254. X    register Tcl_HashEntry *hPtr;
  1255. X    char *result, *p;
  1256. X
  1257. X    /*
  1258. X     * Compute a histogram of bucket usage.
  1259. X     */
  1260. X
  1261. X    for (i = 0; i < NUM_COUNTERS; i++) {
  1262. X    count[i] = 0;
  1263. X    }
  1264. X    overflow = 0;
  1265. X    average = 0.0;
  1266. X    for (i = 0; i < tablePtr->numBuckets; i++) {
  1267. X    j = 0;
  1268. X    for (hPtr = tablePtr->buckets[i]; hPtr != NULL; hPtr = hPtr->nextPtr) {
  1269. X        j++;
  1270. X    }
  1271. X    if (j < NUM_COUNTERS) {
  1272. X        count[j]++;
  1273. X    } else {
  1274. X        overflow++;
  1275. X    }
  1276. X    tmp = j;
  1277. X    average += (tmp+1.0)*(tmp/tablePtr->numEntries)/2.0;
  1278. X    }
  1279. X
  1280. X    /*
  1281. X     * Print out the histogram and a few other pieces of information.
  1282. X     */
  1283. X
  1284. X    result = (char *) ckalloc((unsigned) ((NUM_COUNTERS*60) + 300));
  1285. X    sprintf(result, "%d entries in table, %d buckets\n",
  1286. X        tablePtr->numEntries, tablePtr->numBuckets);
  1287. X    p = result + strlen(result);
  1288. X    for (i = 0; i < NUM_COUNTERS; i++) {
  1289. X    sprintf(p, "number of buckets with %d entries: %d\n",
  1290. X        i, count[i]);
  1291. X    p += strlen(p);
  1292. X    }
  1293. X    sprintf(p, "number of buckets with more %d or more entries: %d\n",
  1294. X        NUM_COUNTERS, overflow);
  1295. X    p += strlen(p);
  1296. X    sprintf(p, "average search distance for entry: %.1f", average);
  1297. X    return result;
  1298. X}
  1299. X
  1300. X/*
  1301. X *----------------------------------------------------------------------
  1302. X *
  1303. X * HashString --
  1304. X *
  1305. X *    Compute a one-word summary of a text string, which can be
  1306. X *    used to generate a hash index.
  1307. X *
  1308. X * Results:
  1309. X *    The return value is a one-word summary of the information in
  1310. X *    string.
  1311. X *
  1312. X * Side effects:
  1313. X *    None.
  1314. X *
  1315. X *----------------------------------------------------------------------
  1316. X */
  1317. X
  1318. Xstatic int
  1319. XHashString(string)
  1320. X    register char *string;    /* String from which to compute hash value. */
  1321. X{
  1322. X    register int result, c;
  1323. X
  1324. X    /*
  1325. X     * I tried a zillion different hash functions and asked many other
  1326. X     * people for advice.  Many people had their own favorite functions,
  1327. X     * all different, but no-one had much idea why they were good ones.
  1328. X     * I chose the one below (multiply by 9 and add new character)
  1329. X     * because of the following reasons:
  1330. X     *
  1331. X     * 1. Multiplying by 10 is perfect for keys that are decimal strings,
  1332. X     *    and multiplying by 9 is just about as good.
  1333. X     * 2. Times-9 is (shift-left-3) plus (old).  This means that each
  1334. X     *    character's bits hang around in the low-order bits of the
  1335. X     *    hash value for ever, plus they spread fairly rapidly up to
  1336. X     *    the high-order bits to fill out the hash value.  This seems
  1337. X     *    works well both for decimal and non-decimal strings.
  1338. X     */
  1339. X
  1340. X    result = 0;
  1341. X    while (1) {
  1342. X    c = *string;
  1343. X    string++;
  1344. X    if (c == 0) {
  1345. X        break;
  1346. X    }
  1347. X    result += (result<<3) + c;
  1348. X    }
  1349. X    return result;
  1350. X}
  1351. X
  1352. X/*
  1353. X *----------------------------------------------------------------------
  1354. X *
  1355. X * StringFind --
  1356. X *
  1357. X *    Given a hash table with string keys, and a string key, find
  1358. X *    the entry with a matching key.
  1359. X *
  1360. X * Results:
  1361. X *    The return value is a token for the matching entry in the
  1362. X *    hash table, or NULL if there was no matching entry.
  1363. X *
  1364. X * Side effects:
  1365. X *    None.
  1366. X *
  1367. X *----------------------------------------------------------------------
  1368. X */
  1369. X
  1370. Xstatic Tcl_HashEntry *
  1371. XStringFind(tablePtr, key)
  1372. X    Tcl_HashTable *tablePtr;    /* Table in which to lookup entry. */
  1373. X    char *key;            /* Key to use to find matching entry. */
  1374. X{
  1375. X    register Tcl_HashEntry *hPtr;
  1376. X    register char *p1, *p2;
  1377. X    int index;
  1378. X
  1379. X    index = HashString(key) & tablePtr->mask;
  1380. X
  1381. X    /*
  1382. X     * Search all of the entries in the appropriate bucket.
  1383. X     */
  1384. X
  1385. X    for (hPtr = tablePtr->buckets[index]; hPtr != NULL;
  1386. X        hPtr = hPtr->nextPtr) {
  1387. X    for (p1 = key, p2 = hPtr->key.string; ; p1++, p2++) {
  1388. X        if (*p1 != *p2) {
  1389. X        break;
  1390. X        }
  1391. X        if (*p1 == '\0') {
  1392. X        return hPtr;
  1393. X        }
  1394. X    }
  1395. X    }
  1396. X    return NULL;
  1397. X}
  1398. X
  1399. X/*
  1400. X *----------------------------------------------------------------------
  1401. X *
  1402. X * StringCreate --
  1403. X *
  1404. X *    Given a hash table with string keys, and a string key, find
  1405. X *    the entry with a matching key.  If there is no matching entry,
  1406. X *    then create a new entry that does match.
  1407. X *
  1408. X * Results:
  1409. X *    The return value is a pointer to the matching entry.  If this
  1410. X *    is a newly-created entry, then *newPtr will be set to a non-zero
  1411. X *    value;  otherwise *newPtr will be set to 0.  If this is a new
  1412. X *    entry the value stored in the entry will initially be 0.
  1413. X *
  1414. X * Side effects:
  1415. X *    A new entry may be added to the hash table.
  1416. X *
  1417. X *----------------------------------------------------------------------
  1418. X */
  1419. X
  1420. Xstatic Tcl_HashEntry *
  1421. XStringCreate(tablePtr, key, newPtr)
  1422. X    Tcl_HashTable *tablePtr;    /* Table in which to lookup entry. */
  1423. X    char *key;            /* Key to use to find or create matching
  1424. X                 * entry. */
  1425. X    int *newPtr;        /* Store info here telling whether a new
  1426. X                 * entry was created. */
  1427. X{
  1428. X    register Tcl_HashEntry *hPtr;
  1429. X    register char *p1, *p2;
  1430. X    int index;
  1431. X
  1432. X    index = HashString(key) & tablePtr->mask;
  1433. X
  1434. X    /*
  1435. X     * Search all of the entries in this bucket.
  1436. X     */
  1437. X
  1438. X    for (hPtr = tablePtr->buckets[index]; hPtr != NULL;
  1439. X        hPtr = hPtr->nextPtr) {
  1440. X    for (p1 = key, p2 = hPtr->key.string; ; p1++, p2++) {
  1441. X        if (*p1 != *p2) {
  1442. X        break;
  1443. X        }
  1444. X        if (*p1 == '\0') {
  1445. X        *newPtr = 0;
  1446. X        return hPtr;
  1447. X        }
  1448. X    }
  1449. X    }
  1450. X
  1451. X    /*
  1452. X     * Entry not found.  Add a new one to the bucket.
  1453. X     */
  1454. X
  1455. X    *newPtr = 1;
  1456. X    hPtr = (Tcl_HashEntry *) ckalloc((unsigned)
  1457. X        (sizeof(Tcl_HashEntry) + strlen(key) - (sizeof(hPtr->key) -1)));
  1458. X    hPtr->tablePtr = tablePtr;
  1459. X    hPtr->bucketPtr = &(tablePtr->buckets[index]);
  1460. X    hPtr->nextPtr = *hPtr->bucketPtr;
  1461. X    hPtr->clientData = 0;
  1462. X    strcpy(hPtr->key.string, key);
  1463. X    *hPtr->bucketPtr = hPtr;
  1464. X    tablePtr->numEntries++;
  1465. X
  1466. X    /*
  1467. X     * If the table has exceeded a decent size, rebuild it with many
  1468. X     * more buckets.
  1469. X     */
  1470. X
  1471. X    if (tablePtr->numEntries >= tablePtr->rebuildSize) {
  1472. X    RebuildTable(tablePtr);
  1473. X    }
  1474. X    return hPtr;
  1475. X}
  1476. X
  1477. X/*
  1478. X *----------------------------------------------------------------------
  1479. X *
  1480. X * OneWordFind --
  1481. X *
  1482. X *    Given a hash table with one-word keys, and a one-word key, find
  1483. X *    the entry with a matching key.
  1484. X *
  1485. X * Results:
  1486. X *    The return value is a token for the matching entry in the
  1487. X *    hash table, or NULL if there was no matching entry.
  1488. X *
  1489. X * Side effects:
  1490. X *    None.
  1491. X *
  1492. X *----------------------------------------------------------------------
  1493. X */
  1494. X
  1495. Xstatic Tcl_HashEntry *
  1496. XOneWordFind(tablePtr, key)
  1497. X    Tcl_HashTable *tablePtr;    /* Table in which to lookup entry. */
  1498. X    register char *key;        /* Key to use to find matching entry. */
  1499. X{
  1500. X    register Tcl_HashEntry *hPtr;
  1501. X    int index;
  1502. X
  1503. X    index = RANDOM_INDEX(tablePtr, key);
  1504. X
  1505. X    /*
  1506. X     * Search all of the entries in the appropriate bucket.
  1507. X     */
  1508. X
  1509. X    for (hPtr = tablePtr->buckets[index]; hPtr != NULL;
  1510. X        hPtr = hPtr->nextPtr) {
  1511. X    if (hPtr->key.oneWordValue == key) {
  1512. X        return hPtr;
  1513. X    }
  1514. X    }
  1515. X    return NULL;
  1516. X}
  1517. X
  1518. X/*
  1519. X *----------------------------------------------------------------------
  1520. X *
  1521. X * OneWordCreate --
  1522. X *
  1523. X *    Given a hash table with one-word keys, and a one-word key, find
  1524. X *    the entry with a matching key.  If there is no matching entry,
  1525. X *    then create a new entry that does match.
  1526. X *
  1527. X * Results:
  1528. X *    The return value is a pointer to the matching entry.  If this
  1529. X *    is a newly-created entry, then *newPtr will be set to a non-zero
  1530. X *    value;  otherwise *newPtr will be set to 0.  If this is a new
  1531. X *    entry the value stored in the entry will initially be 0.
  1532. X *
  1533. X * Side effects:
  1534. X *    A new entry may be added to the hash table.
  1535. X *
  1536. X *----------------------------------------------------------------------
  1537. X */
  1538. X
  1539. Xstatic Tcl_HashEntry *
  1540. XOneWordCreate(tablePtr, key, newPtr)
  1541. X    Tcl_HashTable *tablePtr;    /* Table in which to lookup entry. */
  1542. X    register char *key;        /* Key to use to find or create matching
  1543. X                 * entry. */
  1544. X    int *newPtr;        /* Store info here telling whether a new
  1545. X                 * entry was created. */
  1546. X{
  1547. X    register Tcl_HashEntry *hPtr;
  1548. X    int index;
  1549. X
  1550. X    index = RANDOM_INDEX(tablePtr, key);
  1551. X
  1552. X    /*
  1553. X     * Search all of the entries in this bucket.
  1554. X     */
  1555. X
  1556. X    for (hPtr = tablePtr->buckets[index]; hPtr != NULL;
  1557. X        hPtr = hPtr->nextPtr) {
  1558. X    if (hPtr->key.oneWordValue == key) {
  1559. X        *newPtr = 0;
  1560. X        return hPtr;
  1561. X    }
  1562. X    }
  1563. X
  1564. X    /*
  1565. X     * Entry not found.  Add a new one to the bucket.
  1566. X     */
  1567. X
  1568. X    *newPtr = 1;
  1569. X    hPtr = (Tcl_HashEntry *) ckalloc(sizeof(Tcl_HashEntry));
  1570. X    hPtr->tablePtr = tablePtr;
  1571. X    hPtr->bucketPtr = &(tablePtr->buckets[index]);
  1572. X    hPtr->nextPtr = *hPtr->bucketPtr;
  1573. X    hPtr->clientData = 0;
  1574. X    hPtr->key.oneWordValue = key;
  1575. X    *hPtr->bucketPtr = hPtr;
  1576. X    tablePtr->numEntries++;
  1577. X
  1578. X    /*
  1579. X     * If the table has exceeded a decent size, rebuild it with many
  1580. X     * more buckets.
  1581. X     */
  1582. X
  1583. X    if (tablePtr->numEntries >= tablePtr->rebuildSize) {
  1584. X    RebuildTable(tablePtr);
  1585. X    }
  1586. X    return hPtr;
  1587. X}
  1588. X
  1589. X/*
  1590. X *----------------------------------------------------------------------
  1591. X *
  1592. X * ArrayFind --
  1593. X *
  1594. X *    Given a hash table with array-of-int keys, and a key, find
  1595. X *    the entry with a matching key.
  1596. X *
  1597. X * Results:
  1598. X *    The return value is a token for the matching entry in the
  1599. X *    hash table, or NULL if there was no matching entry.
  1600. X *
  1601. X * Side effects:
  1602. X *    None.
  1603. X *
  1604. X *----------------------------------------------------------------------
  1605. X */
  1606. X
  1607. Xstatic Tcl_HashEntry *
  1608. XArrayFind(tablePtr, key)
  1609. X    Tcl_HashTable *tablePtr;    /* Table in which to lookup entry. */
  1610. X    char *key;            /* Key to use to find matching entry. */
  1611. X{
  1612. X    register Tcl_HashEntry *hPtr;
  1613. X    int *arrayPtr = (int *) key;
  1614. X    register int *iPtr1, *iPtr2;
  1615. X    int index, count;
  1616. X
  1617. X    for (index = 0, count = tablePtr->keyType, iPtr1 = arrayPtr;
  1618. X        count > 0; count--, iPtr1++) {
  1619. X    index += *iPtr1;
  1620. X    }
  1621. X    index = RANDOM_INDEX(tablePtr, index);
  1622. X
  1623. X    /*
  1624. X     * Search all of the entries in the appropriate bucket.
  1625. X     */
  1626. X
  1627. X    for (hPtr = tablePtr->buckets[index]; hPtr != NULL;
  1628. X        hPtr = hPtr->nextPtr) {
  1629. X    for (iPtr1 = arrayPtr, iPtr2 = hPtr->key.words,
  1630. X        count = tablePtr->keyType; ; count--, iPtr1++, iPtr2++) {
  1631. X        if (count == 0) {
  1632. X        return hPtr;
  1633. X        }
  1634. X        if (*iPtr1 != *iPtr2) {
  1635. X        break;
  1636. X        }
  1637. X    }
  1638. X    }
  1639. X    return NULL;
  1640. X}
  1641. X
  1642. X/*
  1643. X *----------------------------------------------------------------------
  1644. X *
  1645. X * ArrayCreate --
  1646. X *
  1647. X *    Given a hash table with one-word keys, and a one-word key, find
  1648. X *    the entry with a matching key.  If there is no matching entry,
  1649. X *    then create a new entry that does match.
  1650. X *
  1651. X * Results:
  1652. X *    The return value is a pointer to the matching entry.  If this
  1653. X *    is a newly-created entry, then *newPtr will be set to a non-zero
  1654. X *    value;  otherwise *newPtr will be set to 0.  If this is a new
  1655. X *    entry the value stored in the entry will initially be 0.
  1656. X *
  1657. X * Side effects:
  1658. X *    A new entry may be added to the hash table.
  1659. X *
  1660. X *----------------------------------------------------------------------
  1661. X */
  1662. X
  1663. Xstatic Tcl_HashEntry *
  1664. XArrayCreate(tablePtr, key, newPtr)
  1665. X    Tcl_HashTable *tablePtr;    /* Table in which to lookup entry. */
  1666. X    register char *key;        /* Key to use to find or create matching
  1667. X                 * entry. */
  1668. X    int *newPtr;        /* Store info here telling whether a new
  1669. X                 * entry was created. */
  1670. X{
  1671. X    register Tcl_HashEntry *hPtr;
  1672. X    int *arrayPtr = (int *) key;
  1673. X    register int *iPtr1, *iPtr2;
  1674. X    int index, count;
  1675. X
  1676. X    for (index = 0, count = tablePtr->keyType, iPtr1 = arrayPtr;
  1677. X        count > 0; count--, iPtr1++) {
  1678. X    index += *iPtr1;
  1679. X    }
  1680. X    index = RANDOM_INDEX(tablePtr, index);
  1681. X
  1682. X    /*
  1683. X     * Search all of the entries in the appropriate bucket.
  1684. X     */
  1685. X
  1686. X    for (hPtr = tablePtr->buckets[index]; hPtr != NULL;
  1687. X        hPtr = hPtr->nextPtr) {
  1688. X    for (iPtr1 = arrayPtr, iPtr2 = hPtr->key.words,
  1689. X        count = tablePtr->keyType; ; count--, iPtr1++, iPtr2++) {
  1690. X        if (count == 0) {
  1691. X        *newPtr = 0;
  1692. X        return hPtr;
  1693. X        }
  1694. X        if (*iPtr1 != *iPtr2) {
  1695. X        break;
  1696. X        }
  1697. X    }
  1698. X    }
  1699. X
  1700. X    /*
  1701. X     * Entry not found.  Add a new one to the bucket.
  1702. X     */
  1703. X
  1704. X    *newPtr = 1;
  1705. X    hPtr = (Tcl_HashEntry *) ckalloc((unsigned) (sizeof(Tcl_HashEntry)
  1706. X        + (tablePtr->keyType*sizeof(int)) - 4));
  1707. X    hPtr->tablePtr = tablePtr;
  1708. X    hPtr->bucketPtr = &(tablePtr->buckets[index]);
  1709. X    hPtr->nextPtr = *hPtr->bucketPtr;
  1710. X    hPtr->clientData = 0;
  1711. X    for (iPtr1 = arrayPtr, iPtr2 = hPtr->key.words, count = tablePtr->keyType;
  1712. X        count > 0; count--, iPtr1++, iPtr2++) {
  1713. X    *iPtr2 = *iPtr1;
  1714. X    }
  1715. X    *hPtr->bucketPtr = hPtr;
  1716. X    tablePtr->numEntries++;
  1717. X
  1718. X    /*
  1719. X     * If the table has exceeded a decent size, rebuild it with many
  1720. X     * more buckets.
  1721. X     */
  1722. X
  1723. X    if (tablePtr->numEntries >= tablePtr->rebuildSize) {
  1724. X    RebuildTable(tablePtr);
  1725. X    }
  1726. X    return hPtr;
  1727. X}
  1728. X
  1729. X/*
  1730. X *----------------------------------------------------------------------
  1731. X *
  1732. X * BogusFind --
  1733. X *
  1734. X *    This procedure is invoked when an Tcl_FindHashEntry is called
  1735. X *    on a table that has been deleted.
  1736. X *
  1737. X * Results:
  1738. X *    If panic returns (which it shouldn't) this procedure returns
  1739. X *    NULL.
  1740. X *
  1741. X * Side effects:
  1742. X *    Generates a panic.
  1743. X *
  1744. X *----------------------------------------------------------------------
  1745. X */
  1746. X
  1747. X    /* ARGSUSED */
  1748. Xstatic Tcl_HashEntry *
  1749. XBogusFind(tablePtr, key)
  1750. X    Tcl_HashTable *tablePtr;    /* Table in which to lookup entry. */
  1751. X    char *key;            /* Key to use to find matching entry. */
  1752. X{
  1753. X    panic("called Tcl_FindHashEntry on deleted table");
  1754. X    return NULL;
  1755. X}
  1756. X
  1757. X/*
  1758. X *----------------------------------------------------------------------
  1759. X *
  1760. X * BogusCreate --
  1761. X *
  1762. X *    This procedure is invoked when an Tcl_CreateHashEntry is called
  1763. X *    on a table that has been deleted.
  1764. X *
  1765. X * Results:
  1766. X *    If panic returns (which it shouldn't) this procedure returns
  1767. X *    NULL.
  1768. X *
  1769. X * Side effects:
  1770. X *    Generates a panic.
  1771. X *
  1772. X *----------------------------------------------------------------------
  1773. X */
  1774. X
  1775. X    /* ARGSUSED */
  1776. Xstatic Tcl_HashEntry *
  1777. XBogusCreate(tablePtr, key, newPtr)
  1778. X    Tcl_HashTable *tablePtr;    /* Table in which to lookup entry. */
  1779. X    char *key;            /* Key to use to find or create matching
  1780. X                 * entry. */
  1781. X    int *newPtr;        /* Store info here telling whether a new
  1782. X                 * entry was created. */
  1783. X{
  1784. X    panic("called Tcl_CreateHashEntry on deleted table");
  1785. X    return NULL;
  1786. X}
  1787. X
  1788. X/*
  1789. X *----------------------------------------------------------------------
  1790. X *
  1791. X * RebuildTable --
  1792. X *
  1793. X *    This procedure is invoked when the ratio of entries to hash
  1794. X *    buckets becomes too large.  It creates a new table with a
  1795. X *    larger bucket array and moves all of the entries into the
  1796. X *    new table.
  1797. X *
  1798. X * Results:
  1799. X *    None.
  1800. X *
  1801. X * Side effects:
  1802. X *    Memory gets reallocated and entries get re-hashed to new
  1803. X *    buckets.
  1804. X *
  1805. X *----------------------------------------------------------------------
  1806. X */
  1807. X
  1808. Xstatic void
  1809. XRebuildTable(tablePtr)
  1810. X    register Tcl_HashTable *tablePtr;    /* Table to enlarge. */
  1811. X{
  1812. X    int oldSize, count, index;
  1813. X    Tcl_HashEntry **oldBuckets;
  1814. X    register Tcl_HashEntry **oldChainPtr, **newChainPtr;
  1815. X    register Tcl_HashEntry *hPtr;
  1816. X
  1817. X    oldSize = tablePtr->numBuckets;
  1818. X    oldBuckets = tablePtr->buckets;
  1819. X
  1820. X    /*
  1821. X     * Allocate and initialize the new bucket array, and set up
  1822. X     * hashing constants for new array size.
  1823. X     */
  1824. X
  1825. X    tablePtr->numBuckets *= 4;
  1826. X    tablePtr->buckets = (Tcl_HashEntry **) ckalloc((unsigned)
  1827. X        (tablePtr->numBuckets * sizeof(Tcl_HashEntry *)));
  1828. X    for (count = tablePtr->numBuckets, newChainPtr = tablePtr->buckets;
  1829. X        count > 0; count--, newChainPtr++) {
  1830. X    *newChainPtr = NULL;
  1831. X    }
  1832. X    tablePtr->rebuildSize *= 4;
  1833. X    tablePtr->downShift -= 2;
  1834. X    tablePtr->mask = (tablePtr->mask << 2) + 3;
  1835. X
  1836. X    /*
  1837. X     * Rehash all of the existing entries into the new bucket array.
  1838. X     */
  1839. X
  1840. X    for (oldChainPtr = oldBuckets; oldSize > 0; oldSize--, oldChainPtr++) {
  1841. X    for (hPtr = *oldChainPtr; hPtr != NULL; hPtr = *oldChainPtr) {
  1842. X        *oldChainPtr = hPtr->nextPtr;
  1843. X        if (tablePtr->keyType == TCL_STRING_KEYS) {
  1844. X        index = HashString(hPtr->key.string) & tablePtr->mask;
  1845. X        } else if (tablePtr->keyType == TCL_ONE_WORD_KEYS) {
  1846. X        index = RANDOM_INDEX(tablePtr, hPtr->key.oneWordValue);
  1847. X        } else {
  1848. X        register int *iPtr;
  1849. X        int count;
  1850. X
  1851. X        for (index = 0, count = tablePtr->keyType,
  1852. X            iPtr = hPtr->key.words; count > 0; count--, iPtr++) {
  1853. X            index += *iPtr;
  1854. X        }
  1855. X        index = RANDOM_INDEX(tablePtr, index);
  1856. X        }
  1857. X        hPtr->bucketPtr = &(tablePtr->buckets[index]);
  1858. X        hPtr->nextPtr = *hPtr->bucketPtr;
  1859. X        *hPtr->bucketPtr = hPtr;
  1860. X    }
  1861. X    }
  1862. X
  1863. X    /*
  1864. X     * Free up the old bucket array, if it was dynamically allocated.
  1865. X     */
  1866. X
  1867. X    if (oldBuckets != tablePtr->staticBuckets) {
  1868. X    ckfree((char *) oldBuckets);
  1869. X    }
  1870. X}
  1871. END_OF_FILE
  1872. if test 25018 -ne `wc -c <'tcl6.1/tclHash.c'`; then
  1873.     echo shar: \"'tcl6.1/tclHash.c'\" unpacked with wrong size!
  1874. fi
  1875. # end of 'tcl6.1/tclHash.c'
  1876. fi
  1877. echo shar: End of archive 16 \(of 33\).
  1878. cp /dev/null ark16isdone
  1879. MISSING=""
  1880. 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
  1881.     if test ! -f ark${I}isdone ; then
  1882.     MISSING="${MISSING} ${I}"
  1883.     fi
  1884. done
  1885. if test "${MISSING}" = "" ; then
  1886.     echo You have unpacked all 33 archives.
  1887.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1888. else
  1889.     echo You still need to unpack the following archives:
  1890.     echo "        " ${MISSING}
  1891. fi
  1892. ##  End of shell archive.
  1893. exit 0
  1894.  
  1895. exit 0 # Just in case...
  1896. -- 
  1897. Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
  1898. Sterling Software, IMD           UUCP:     uunet!sparky!kent
  1899. Phone:    (402) 291-8300         FAX:      (402) 291-4362
  1900. Please send comp.sources.misc-related mail to kent@uunet.uu.net.
  1901.