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

  1. Newsgroups: comp.sources.misc
  2. From: karl@sugar.neosoft.com (Karl Lehenbauer)
  3. Subject:  v25i092:  tcl - tool command language, version 6.1, Part24/33
  4. Message-ID: <1991Nov15.225423.21555@sparky.imd.sterling.com>
  5. X-Md4-Signature: 08422b25a4efa15eaae789f3488c2154
  6. Date: Fri, 15 Nov 1991 22:54:23 GMT
  7. Approved: kent@sparky.imd.sterling.com
  8.  
  9. Submitted-by: karl@sugar.neosoft.com (Karl Lehenbauer)
  10. Posting-number: Volume 25, Issue 92
  11. Archive-name: tcl/part24
  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 24 (of 33)."
  21. # Contents:  tcl6.1/tclParse.c
  22. # Wrapped by karl@one on Tue Nov 12 19:44:29 1991
  23. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  24. if test -f 'tcl6.1/tclParse.c' -a "${1}" != "-c" ; then 
  25.   echo shar: Will not clobber existing file \"'tcl6.1/tclParse.c'\"
  26. else
  27. echo shar: Extracting \"'tcl6.1/tclParse.c'\" \(32623 characters\)
  28. sed "s/^X//" >'tcl6.1/tclParse.c' <<'END_OF_FILE'
  29. X/* 
  30. X * tclParse.c --
  31. X *
  32. X *    This file contains a collection of procedures that are used
  33. X *    to parse Tcl commands or parts of commands (like quoted
  34. X *    strings or nested sub-commands).
  35. X *
  36. X * Copyright 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/tclParse.c,v 1.20 91/10/31 16:41:52 ouster Exp $ SPRITE (Berkeley)";
  48. X#endif
  49. X
  50. X#include "tclInt.h"
  51. X
  52. X/*
  53. X * The following table assigns a type to each character.  Only types
  54. X * meaningful to Tcl parsing are represented here.  The table indexes
  55. X * all 256 characters, with the negative ones first, then the positive
  56. X * ones.
  57. X */
  58. X
  59. Xchar tclTypeTable[] = {
  60. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  61. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  62. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  63. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  64. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  65. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  66. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  67. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  68. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  69. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  70. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  71. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  72. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  73. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  74. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  75. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  76. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  77. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  78. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  79. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  80. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  81. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  82. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  83. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  84. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  85. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  86. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  87. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  88. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  89. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  90. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  91. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  92. X    TCL_COMMAND_END,   TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  93. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  94. X    TCL_NORMAL,        TCL_SPACE,         TCL_COMMAND_END,   TCL_SPACE,
  95. X    TCL_SPACE,         TCL_SPACE,         TCL_NORMAL,        TCL_NORMAL,
  96. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  97. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  98. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  99. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  100. X    TCL_SPACE,         TCL_NORMAL,        TCL_QUOTE,         TCL_NORMAL,
  101. X    TCL_DOLLAR,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  102. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  103. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  104. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  105. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  106. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_COMMAND_END,
  107. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  108. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  109. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  110. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  111. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  112. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  113. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  114. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_OPEN_BRACKET,
  115. X    TCL_BACKSLASH,     TCL_COMMAND_END,   TCL_NORMAL,        TCL_NORMAL,
  116. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  117. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  118. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  119. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  120. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  121. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,
  122. X    TCL_NORMAL,        TCL_NORMAL,        TCL_NORMAL,        TCL_OPEN_BRACE,
  123. X    TCL_NORMAL,        TCL_CLOSE_BRACE,   TCL_NORMAL,        TCL_NORMAL,
  124. X};
  125. X
  126. X/*
  127. X * Function prototypes for procedures local to this file:
  128. X */
  129. X
  130. Xstatic char *    QuoteEnd _ANSI_ARGS_((char *string, int term));
  131. Xstatic char *    VarNameEnd _ANSI_ARGS_((char *string));
  132. X
  133. X/*
  134. X *----------------------------------------------------------------------
  135. X *
  136. X * Tcl_Backslash --
  137. X *
  138. X *    Figure out how to handle a backslash sequence.
  139. X *
  140. X * Results:
  141. X *    The return value is the character that should be substituted
  142. X *    in place of the backslash sequence that starts at src, or 0
  143. X *    if the backslash sequence should be replace by nothing (e.g.
  144. X *    backslash followed by newline).  If readPtr isn't NULL then
  145. X *    it is filled in with a count of the number of characters in
  146. X *    the backslash sequence.  Note:  if the backslash isn't followed
  147. X *    by characters that are understood here, then the backslash
  148. X *    sequence is only considered to be one character long, and it
  149. X *    is replaced by a backslash char.
  150. X *
  151. X * Side effects:
  152. X *    None.
  153. X *
  154. X *----------------------------------------------------------------------
  155. X */
  156. X
  157. Xchar
  158. XTcl_Backslash(src, readPtr)
  159. X    char *src;            /* Points to the backslash character of
  160. X                 * a backslash sequence. */
  161. X    int *readPtr;        /* Fill in with number of characters read
  162. X                 * from src, unless NULL. */
  163. X{
  164. X    register char *p = src+1;
  165. X    char result;
  166. X    int count;
  167. X
  168. X    count = 2;
  169. X
  170. X    switch (*p) {
  171. X    case 'b':
  172. X        result = '\b';
  173. X        break;
  174. X    case 'e':
  175. X        result = 033;
  176. X        break;
  177. X    case 'f':
  178. X        result = '\f';
  179. X        break;
  180. X    case 'n':
  181. X        result = '\n';
  182. X        break;
  183. X    case 'r':
  184. X        result = '\r';
  185. X        break;
  186. X    case 't':
  187. X        result = '\t';
  188. X        break;
  189. X    case 'v':
  190. X        result = '\v';
  191. X        break;
  192. X    case 'C':
  193. X        p++;
  194. X        if (isspace(*p) || (*p == 0)) {
  195. X        result = 'C';
  196. X        count = 1;
  197. X        break;
  198. X        }
  199. X        count = 3;
  200. X        if (*p == 'M') {
  201. X        p++;
  202. X        if (isspace(*p) || (*p == 0)) {
  203. X            result = 'M' & 037;
  204. X            break;
  205. X        }
  206. X        count = 4;
  207. X        result = (*p & 037) | 0200;
  208. X        break;
  209. X        }
  210. X        count = 3;
  211. X        result = *p & 037;
  212. X        break;
  213. X    case 'M':
  214. X        p++;
  215. X        if (isspace(*p) || (*p == 0)) {
  216. X        result = 'M';
  217. X        count = 1;
  218. X        break;
  219. X        }
  220. X        count = 3;
  221. X        result = *p + 0200;
  222. X        break;
  223. X    case '}':
  224. X    case '{':
  225. X    case ']':
  226. X    case '[':
  227. X    case '$':
  228. X    case ' ':
  229. X    case ';':
  230. X    case '"':
  231. X    case '\\':
  232. X        result = *p;
  233. X        break;
  234. X    case '\n':
  235. X        result = 0;
  236. X        break;
  237. X    default:
  238. X        if (isdigit(*p)) {
  239. X        result = *p - '0';
  240. X        p++;
  241. X        if (!isdigit(*p)) {
  242. X            break;
  243. X        }
  244. X        count = 3;
  245. X        result = (result << 3) + (*p - '0');
  246. X        p++;
  247. X        if (!isdigit(*p)) {
  248. X            break;
  249. X        }
  250. X        count = 4;
  251. X        result = (result << 3) + (*p - '0');
  252. X        break;
  253. X        }
  254. X        result = '\\';
  255. X        count = 1;
  256. X        break;
  257. X    }
  258. X
  259. X    if (readPtr != NULL) {
  260. X    *readPtr = count;
  261. X    }
  262. X    return result;
  263. X}
  264. X
  265. X/*
  266. X *--------------------------------------------------------------
  267. X *
  268. X * TclParseQuotes --
  269. X *
  270. X *    This procedure parses a double-quoted string such as a
  271. X *    quoted Tcl command argument or a quoted value in a Tcl
  272. X *    expression.  This procedure is also used to parse array
  273. X *    element names within parentheses, or anything else that
  274. X *    needs all the substitutions that happen in quotes.
  275. X *
  276. X * Results:
  277. X *    The return value is a standard Tcl result, which is
  278. X *    TCL_OK unless there was an error while parsing the
  279. X *    quoted string.  If an error occurs then interp->result
  280. X *    contains a standard error message.  *TermPtr is filled
  281. X *    in with the address of the character just after the
  282. X *    last one successfully processed;  this is usually the
  283. X *    character just after the matching close-quote.  The
  284. X *    fully-substituted contents of the quotes are stored in
  285. X *    standard fashion in *pvPtr, null-terminated with
  286. X *    pvPtr->next pointing to the terminating null character.
  287. X *
  288. X * Side effects:
  289. X *    The buffer space in pvPtr may be enlarged by calling its
  290. X *    expandProc.
  291. X *
  292. X *--------------------------------------------------------------
  293. X */
  294. X
  295. Xint
  296. XTclParseQuotes(interp, string, termChar, flags, termPtr, pvPtr)
  297. X    Tcl_Interp *interp;        /* Interpreter to use for nested command
  298. X                 * evaluations and error messages. */
  299. X    char *string;        /* Character just after opening double-
  300. X                 * quote. */
  301. X    int termChar;        /* Character that terminates "quoted" string
  302. X                 * (usually double-quote, but sometimes
  303. X                 * right-paren or something else). */
  304. X    int flags;            /* Flags to pass to nested Tcl_Eval calls. */
  305. X    char **termPtr;        /* Store address of terminating character
  306. X                 * here. */
  307. X    ParseValue *pvPtr;        /* Information about where to place
  308. X                 * fully-substituted result of parse. */
  309. X{
  310. X    register char *src, *dst, c;
  311. X
  312. X    src = string;
  313. X    dst = pvPtr->next;
  314. X
  315. X    while (1) {
  316. X    if (dst == pvPtr->end) {
  317. X        /*
  318. X         * Target buffer space is about to run out.  Make more space.
  319. X         */
  320. X
  321. X        pvPtr->next = dst;
  322. X        (*pvPtr->expandProc)(pvPtr, 1);
  323. X        dst = pvPtr->next;
  324. X    }
  325. X
  326. X    c = *src;
  327. X    src++;
  328. X    if (c == termChar) {
  329. X        *dst = '\0';
  330. X        pvPtr->next = dst;
  331. X        *termPtr = src;
  332. X        return TCL_OK;
  333. X    } else if (CHAR_TYPE(c) == TCL_NORMAL) {
  334. X        copy:
  335. X        *dst = c;
  336. X        dst++;
  337. X        continue;
  338. X    } else if (c == '$') {
  339. X        int length;
  340. X        char *value;
  341. X
  342. X        value = Tcl_ParseVar(interp, src-1, termPtr);
  343. X        if (value == NULL) {
  344. X        return TCL_ERROR;
  345. X        }
  346. X        src = *termPtr;
  347. X        length = strlen(value);
  348. X        if ((pvPtr->end - dst) <= length) {
  349. X        pvPtr->next = dst;
  350. X        (*pvPtr->expandProc)(pvPtr, length);
  351. X        dst = pvPtr->next;
  352. X        }
  353. X        strcpy(dst, value);
  354. X        dst += length;
  355. X        continue;
  356. X    } else if (c == '[') {
  357. X        int result;
  358. X
  359. X        pvPtr->next = dst;
  360. X        result = TclParseNestedCmd(interp, src, flags, termPtr, pvPtr);
  361. X        if (result != TCL_OK) {
  362. X        return result;
  363. X        }
  364. X        src = *termPtr;
  365. X        dst = pvPtr->next;
  366. X        continue;
  367. X    } else if (c == '\\') {
  368. X        int numRead;
  369. X
  370. X        src--;
  371. X        *dst = Tcl_Backslash(src, &numRead);
  372. X        if (*dst != 0) {
  373. X        dst++;
  374. X        }
  375. X        src += numRead;
  376. X        continue;
  377. X    } else if (c == '\0') {
  378. X        Tcl_ResetResult(interp);
  379. X        sprintf(interp->result, "missing %c", termChar);
  380. X        *termPtr = string-1;
  381. X        return TCL_ERROR;
  382. X    } else {
  383. X        goto copy;
  384. X    }
  385. X    }
  386. X}
  387. X
  388. X/*
  389. X *--------------------------------------------------------------
  390. X *
  391. X * TclParseNestedCmd --
  392. X *
  393. X *    This procedure parses a nested Tcl command between
  394. X *    brackets, returning the result of the command.
  395. X *
  396. X * Results:
  397. X *    The return value is a standard Tcl result, which is
  398. X *    TCL_OK unless there was an error while executing the
  399. X *    nested command.  If an error occurs then interp->result
  400. X *    contains a standard error message.  *TermPtr is filled
  401. X *    in with the address of the character just after the
  402. X *    last one processed;  this is usually the character just
  403. X *    after the matching close-bracket, or the null character
  404. X *    at the end of the string if the close-bracket was missing
  405. X *    (a missing close bracket is an error).  The result returned
  406. X *    by the command is stored in standard fashion in *pvPtr,
  407. X *    null-terminated, with pvPtr->next pointing to the null
  408. X *    character.
  409. X *
  410. X * Side effects:
  411. X *    The storage space at *pvPtr may be expanded.
  412. X *
  413. X *--------------------------------------------------------------
  414. X */
  415. X
  416. Xint
  417. XTclParseNestedCmd(interp, string, flags, termPtr, pvPtr)
  418. X    Tcl_Interp *interp;        /* Interpreter to use for nested command
  419. X                 * evaluations and error messages. */
  420. X    char *string;        /* Character just after opening bracket. */
  421. X    int flags;            /* Flags to pass to nested Tcl_Eval. */
  422. X    char **termPtr;        /* Store address of terminating character
  423. X                 * here. */
  424. X    register ParseValue *pvPtr;    /* Information about where to place
  425. X                 * result of command. */
  426. X{
  427. X    int result, length, shortfall;
  428. X    Interp *iPtr = (Interp *) interp;
  429. X
  430. X    result = Tcl_Eval(interp, string, flags | TCL_BRACKET_TERM, termPtr);
  431. X    if (result != TCL_OK) {
  432. X    /*
  433. X     * The increment below results in slightly cleaner message in
  434. X     * the errorInfo variable (the close-bracket will appear).
  435. X     */
  436. X
  437. X    if (**termPtr == ']') {
  438. X        *termPtr += 1;
  439. X    }
  440. X    return result;
  441. X    }
  442. X    (*termPtr) += 1;
  443. X    length = strlen(iPtr->result);
  444. X    shortfall = length + 1 - (pvPtr->end - pvPtr->next);
  445. X    if (shortfall > 0) {
  446. X    (*pvPtr->expandProc)(pvPtr, shortfall);
  447. X    }
  448. X    strcpy(pvPtr->next, iPtr->result);
  449. X    pvPtr->next += length;
  450. X    Tcl_FreeResult(iPtr);
  451. X    iPtr->result = iPtr->resultSpace;
  452. X    iPtr->resultSpace[0] = '\0';
  453. X    return TCL_OK;
  454. X}
  455. X
  456. X/*
  457. X *--------------------------------------------------------------
  458. X *
  459. X * TclParseBraces --
  460. X *
  461. X *    This procedure scans the information between matching
  462. X *    curly braces.
  463. X *
  464. X * Results:
  465. X *    The return value is a standard Tcl result, which is
  466. X *    TCL_OK unless there was an error while parsing string.
  467. X *    If an error occurs then interp->result contains a
  468. X *    standard error message.  *TermPtr is filled
  469. X *    in with the address of the character just after the
  470. X *    last one successfully processed;  this is usually the
  471. X *    character just after the matching close-brace.  The
  472. X *    information between curly braces is stored in standard
  473. X *    fashion in *pvPtr, null-terminated with pvPtr->next
  474. X *    pointing to the terminating null character.
  475. X *
  476. X * Side effects:
  477. X *    The storage space at *pvPtr may be expanded.
  478. X *
  479. X *--------------------------------------------------------------
  480. X */
  481. X
  482. Xint
  483. XTclParseBraces(interp, string, termPtr, pvPtr)
  484. X    Tcl_Interp *interp;        /* Interpreter to use for nested command
  485. X                 * evaluations and error messages. */
  486. X    char *string;        /* Character just after opening bracket. */
  487. X    char **termPtr;        /* Store address of terminating character
  488. X                 * here. */
  489. X    register ParseValue *pvPtr;    /* Information about where to place
  490. X                 * result of command. */
  491. X{
  492. X    int level;
  493. X    register char *src, *dst, *end;
  494. X    register char c;
  495. X
  496. X    src = string;
  497. X    dst = pvPtr->next;
  498. X    end = pvPtr->end;
  499. X    level = 1;
  500. X
  501. X    /*
  502. X     * Copy the characters one at a time to the result area, stopping
  503. X     * when the matching close-brace is found.
  504. X     */
  505. X
  506. X    while (1) {
  507. X    c = *src;
  508. X    src++;
  509. X    if (dst == end) {
  510. X        pvPtr->next = dst;
  511. X        (*pvPtr->expandProc)(pvPtr, 20);
  512. X        dst = pvPtr->next;
  513. X        end = pvPtr->end;
  514. X    }
  515. X    *dst = c;
  516. X    dst++;
  517. X    if (CHAR_TYPE(c) == TCL_NORMAL) {
  518. X        continue;
  519. X    } else if (c == '{') {
  520. X        level++;
  521. X    } else if (c == '}') {
  522. X        level--;
  523. X        if (level == 0) {
  524. X        dst--;            /* Don't copy the last close brace. */
  525. X        break;
  526. X        }
  527. X    } else if (c == '\\') {
  528. X        int count;
  529. X
  530. X        /*
  531. X         * Must always squish out backslash-newlines, even when in
  532. X         * braces.  This is needed so that this sequence can appear
  533. X         * anywhere in a command, such as the middle of an expression.
  534. X         */
  535. X
  536. X        if (*src == '\n') {
  537. X        dst--;
  538. X        src++;
  539. X        } else {
  540. X        (void) Tcl_Backslash(src-1, &count);
  541. X        while (count > 1) {
  542. X                    if (dst == end) {
  543. X                        pvPtr->next = dst;
  544. X                        (*pvPtr->expandProc)(pvPtr, 20);
  545. X                        dst = pvPtr->next;
  546. X                        end = pvPtr->end;
  547. X                    }
  548. X            *dst = *src;
  549. X            dst++;
  550. X            src++;
  551. X            count--;
  552. X        }
  553. X        }
  554. X    } else if (c == '\0') {
  555. X        Tcl_SetResult(interp, "missing close-brace", TCL_STATIC);
  556. X        *termPtr = string-1;
  557. X        return TCL_ERROR;
  558. X    }
  559. X    }
  560. X
  561. X    *dst = '\0';
  562. X    pvPtr->next = dst;
  563. X    *termPtr = src;
  564. X    return TCL_OK;
  565. X}
  566. X
  567. X/*
  568. X *--------------------------------------------------------------
  569. X *
  570. X * TclParseWords --
  571. X *
  572. X *    This procedure parses one or more words from a command
  573. X *    string and creates argv-style pointers to fully-substituted
  574. X *    copies of those words.
  575. X *
  576. X * Results:
  577. X *    The return value is a standard Tcl result.
  578. X *    
  579. X *    *argcPtr is modified to hold a count of the number of words
  580. X *    successfully parsed, which may be 0.  At most maxWords words
  581. X *    will be parsed.  If 0 <= *argcPtr < maxWords then it
  582. X *    means that a command separator was seen.  If *argcPtr
  583. X *    is maxWords then it means that a command separator was
  584. X *    not seen yet.
  585. X *
  586. X *    *TermPtr is filled in with the address of the character
  587. X *    just after the last one successfully processed in the
  588. X *    last word.  This is either the command terminator (if
  589. X *    *argcPtr < maxWords), the character just after the last
  590. X *    one in a word (if *argcPtr is maxWords), or the vicinity
  591. X *    of an error (if the result is not TCL_OK).
  592. X *    
  593. X *    The pointers at *argv are filled in with pointers to the
  594. X *    fully-substituted words, and the actual contents of the
  595. X *    words are copied to the buffer at pvPtr.
  596. X *
  597. X *    If an error occurrs then an error message is left in
  598. X *    interp->result and the information at *argv, *argcPtr,
  599. X *    and *pvPtr may be incomplete.
  600. X *
  601. X * Side effects:
  602. X *    The buffer space in pvPtr may be enlarged by calling its
  603. X *    expandProc.
  604. X *
  605. X *--------------------------------------------------------------
  606. X */
  607. X
  608. Xint
  609. XTclParseWords(interp, string, flags, maxWords, termPtr, argcPtr, argv, pvPtr)
  610. X    Tcl_Interp *interp;        /* Interpreter to use for nested command
  611. X                 * evaluations and error messages. */
  612. X    char *string;        /* First character of word. */
  613. X    int flags;            /* Flags to control parsing (same values as
  614. X                 * passed to Tcl_Eval). */
  615. X    int maxWords;        /* Maximum number of words to parse. */
  616. X    char **termPtr;        /* Store address of terminating character
  617. X                 * here. */
  618. X    int *argcPtr;        /* Filled in with actual number of words
  619. X                 * parsed. */
  620. X    char **argv;        /* Store addresses of individual words here. */
  621. X    register ParseValue *pvPtr;    /* Information about where to place
  622. X                 * fully-substituted word. */
  623. X{
  624. X    register char *src, *dst;
  625. X    register char c;
  626. X    int type, result, argc;
  627. X    char *oldBuffer;        /* Used to detect when pvPtr's buffer gets
  628. X                 * reallocated, so we can adjust all of the
  629. X                 * argv pointers. */
  630. X
  631. X    src = string;
  632. X    oldBuffer = pvPtr->buffer;
  633. X    dst = pvPtr->next;
  634. X    for (argc = 0; argc < maxWords; argc++) {
  635. X    argv[argc] = dst;
  636. X
  637. X    /*
  638. X     * Skip leading space.
  639. X     */
  640. X    
  641. X    skipSpace:
  642. X    c = *src;
  643. X    type = CHAR_TYPE(c);
  644. X    while (type == TCL_SPACE) {
  645. X        src++;
  646. X        c = *src;
  647. X        type = CHAR_TYPE(c);
  648. X    }
  649. X    
  650. X    /*
  651. X     * Handle the normal case (i.e. no leading double-quote or brace).
  652. X     */
  653. X
  654. X    if (type == TCL_NORMAL) {
  655. X        normalArg:
  656. X        while (1) {
  657. X        if (dst == pvPtr->end) {
  658. X            /*
  659. X             * Target buffer space is about to run out.  Make
  660. X             * more space.
  661. X             */
  662. X    
  663. X            pvPtr->next = dst;
  664. X            (*pvPtr->expandProc)(pvPtr, 1);
  665. X            dst = pvPtr->next;
  666. X        }
  667. X    
  668. X        if (type == TCL_NORMAL) {
  669. X            copy:
  670. X            *dst = c;
  671. X            dst++;
  672. X            src++;
  673. X        } else if (type == TCL_SPACE) {
  674. X            goto wordEnd;
  675. X        } else if (type == TCL_DOLLAR) {
  676. X            int length;
  677. X            char *value;
  678. X    
  679. X            value = Tcl_ParseVar(interp, src, termPtr);
  680. X            if (value == NULL) {
  681. X            return TCL_ERROR;
  682. X            }
  683. X            src = *termPtr;
  684. X            length = strlen(value);
  685. X            if ((pvPtr->end - dst) <= length) {
  686. X            pvPtr->next = dst;
  687. X            (*pvPtr->expandProc)(pvPtr, length);
  688. X            dst = pvPtr->next;
  689. X            }
  690. X            strcpy(dst, value);
  691. X            dst += length;
  692. X        } else if (type == TCL_COMMAND_END) {
  693. X            if ((c == ']') && !(flags & TCL_BRACKET_TERM)) {
  694. X            goto copy;
  695. X            }
  696. X
  697. X            /*
  698. X             * End of command;  simulate a word-end first, so
  699. X             * that the end-of-command can be processed as the
  700. X             * first thing in a new word.
  701. X             */
  702. X
  703. X            goto wordEnd;
  704. X        } else if (type == TCL_OPEN_BRACKET) {
  705. X            pvPtr->next = dst;
  706. X            result = TclParseNestedCmd(interp, src+1, flags, termPtr,
  707. X                pvPtr);
  708. X            if (result != TCL_OK) {
  709. X            return result;
  710. X            }
  711. X            src = *termPtr;
  712. X            dst = pvPtr->next;
  713. X        } else if (type == TCL_BACKSLASH) {
  714. X            int numRead;
  715. X    
  716. X            *dst = Tcl_Backslash(src, &numRead);
  717. X            if (*dst != 0) {
  718. X            dst++;
  719. X            }
  720. X            src += numRead;
  721. X        } else {
  722. X            goto copy;
  723. X        }
  724. X        c = *src;
  725. X        type = CHAR_TYPE(c);
  726. X        }
  727. X    } else {
  728. X    
  729. X        /*
  730. X         * Check for the end of the command.
  731. X         */
  732. X    
  733. X        if (type == TCL_COMMAND_END) {
  734. X        if (flags & TCL_BRACKET_TERM) {
  735. X            if (c == '\0') {
  736. X            Tcl_SetResult(interp, "missing close-bracket",
  737. X                TCL_STATIC);
  738. X            return TCL_ERROR;
  739. X            }
  740. X        } else {
  741. X            if (c == ']') {
  742. X            goto normalArg;
  743. X            }
  744. X        }
  745. X        goto done;
  746. X        }
  747. X    
  748. X        /*
  749. X         * Now handle the special cases: open braces, double-quotes,
  750. X         * and backslash-newline.
  751. X         */
  752. X
  753. X        pvPtr->next = dst;
  754. X        if (type == TCL_QUOTE) {
  755. X        result = TclParseQuotes(interp, src+1, '"', flags,
  756. X            termPtr, pvPtr);
  757. X        } else if (type == TCL_OPEN_BRACE) {
  758. X        result = TclParseBraces(interp, src+1, termPtr, pvPtr);
  759. X        } else if ((type == TCL_BACKSLASH) && (src[1] == '\n')) {
  760. X        src += 2;
  761. X        goto skipSpace;
  762. X        } else {
  763. X        goto normalArg;
  764. X        }
  765. X        if (result != TCL_OK) {
  766. X        return result;
  767. X        }
  768. X    
  769. X        /*
  770. X         * Back from quotes or braces;  make sure that the terminating
  771. X         * character was the end of the word.  Have to be careful here
  772. X         * to handle continuation lines (i.e. lines ending in backslash).
  773. X         */
  774. X    
  775. X        c = **termPtr;
  776. X        if ((c == '\\') && ((*termPtr)[1] == '\n')) {
  777. X        c = (*termPtr)[2];
  778. X        }
  779. X        type = CHAR_TYPE(c);
  780. X        if ((type != TCL_SPACE) && (type != TCL_COMMAND_END)) {
  781. X        if (*src == '"') {
  782. X            Tcl_SetResult(interp, "extra characters after close-quote",
  783. X                TCL_STATIC);
  784. X        } else {
  785. X            Tcl_SetResult(interp, "extra characters after close-brace",
  786. X                TCL_STATIC);
  787. X        }
  788. X        return TCL_ERROR;
  789. X        }
  790. X        src = *termPtr;
  791. X        dst = pvPtr->next;
  792. X
  793. X    }
  794. X
  795. X    /*
  796. X     * We're at the end of a word, so add a null terminator.  Then
  797. X     * see if the buffer was re-allocated during this word.  If so,
  798. X     * update all of the argv pointers.
  799. X     */
  800. X
  801. X    wordEnd:
  802. X    *dst = '\0';
  803. X    dst++;
  804. X    if (oldBuffer != pvPtr->buffer) {
  805. X        int i;
  806. X
  807. X        for (i = 0; i <= argc; i++) {
  808. X        argv[i] = pvPtr->buffer + (argv[i] - oldBuffer);
  809. X        }
  810. X        oldBuffer = pvPtr->buffer;
  811. X    }
  812. X    }
  813. X
  814. X    done:
  815. X    pvPtr->next = dst;
  816. X    *termPtr = src;
  817. X    *argcPtr = argc;
  818. X    return TCL_OK;
  819. X}
  820. X
  821. X/*
  822. X *--------------------------------------------------------------
  823. X *
  824. X * TclExpandParseValue --
  825. X *
  826. X *    This procedure is commonly used as the value of the
  827. X *    expandProc in a ParseValue.  It uses malloc to allocate
  828. X *    more space for the result of a parse.
  829. X *
  830. X * Results:
  831. X *    The buffer space in *pvPtr is reallocated to something
  832. X *    larger, and if pvPtr->clientData is non-zero the old
  833. X *    buffer is freed.  Information is copied from the old
  834. X *    buffer to the new one.
  835. X *
  836. X * Side effects:
  837. X *    None.
  838. X *
  839. X *--------------------------------------------------------------
  840. X */
  841. X
  842. Xvoid
  843. XTclExpandParseValue(pvPtr, needed)
  844. X    register ParseValue *pvPtr;        /* Information about buffer that
  845. X                     * must be expanded.  If the clientData
  846. X                     * in the structure is non-zero, it
  847. X                     * means that the current buffer is
  848. X                     * dynamically allocated. */
  849. X    int needed;                /* Minimum amount of additional space
  850. X                     * to allocate. */
  851. X{
  852. X    int newSpace;
  853. X    char *new;
  854. X
  855. X    /*
  856. X     * Either double the size of the buffer or add enough new space
  857. X     * to meet the demand, whichever produces a larger new buffer.
  858. X     */
  859. X
  860. X    newSpace = (pvPtr->end - pvPtr->buffer) + 1;
  861. X    if (newSpace < needed) {
  862. X    newSpace += needed;
  863. X    } else {
  864. X    newSpace += newSpace;
  865. X    }
  866. X    new = (char *) ckalloc((unsigned) newSpace);
  867. X
  868. X    /*
  869. X     * Copy from old buffer to new, free old buffer if needed, and
  870. X     * mark new buffer as malloc-ed.
  871. X     */
  872. X
  873. X    memcpy((VOID *) new, (VOID *) pvPtr->buffer, pvPtr->next - pvPtr->buffer);
  874. X    pvPtr->next = new + (pvPtr->next - pvPtr->buffer);
  875. X    if (pvPtr->clientData != 0) {
  876. X    ckfree(pvPtr->buffer);
  877. X    }
  878. X    pvPtr->buffer = new;
  879. X    pvPtr->end = new + newSpace - 1;
  880. X    pvPtr->clientData = (ClientData) 1;
  881. X}
  882. X
  883. X/*
  884. X *----------------------------------------------------------------------
  885. X *
  886. X * TclWordEnd --
  887. X *
  888. X *    Given a pointer into a Tcl command, find the end of the next
  889. X *    word of the command.
  890. X *
  891. X * Results:
  892. X *    The return value is a pointer to the character just after the
  893. X *    last one that's part of the word pointed to by "start".  This
  894. X *    may be the address of the NULL character at the end of the
  895. X *    string.
  896. X *
  897. X * Side effects:
  898. X *    None.
  899. X *
  900. X *----------------------------------------------------------------------
  901. X */
  902. X
  903. Xchar *
  904. XTclWordEnd(start, nested)
  905. X    char *start;        /* Beginning of a word of a Tcl command. */
  906. X    int nested;            /* Zero means this is a top-level command.
  907. X                 * One means this is a nested command (close
  908. X                 * brace is a word terminator). */
  909. X{
  910. X    register char *p;
  911. X    int count;
  912. X
  913. X    p = start;
  914. X    while (isspace(*p)) {
  915. X    p++;
  916. X    }
  917. X
  918. X    /*
  919. X     * Handle words beginning with a double-quote or a brace.
  920. X     */
  921. X
  922. X    if (*p == '"') {
  923. X    p = QuoteEnd(p+1, '"');
  924. X    } else if (*p == '{') {
  925. X    int braces = 1;
  926. X    while (braces != 0) {
  927. X        p++;
  928. X        while (*p == '\\') {
  929. X        (void) Tcl_Backslash(p, &count);
  930. X        p += count;
  931. X        }
  932. X        if (*p == '}') {
  933. X        braces--;
  934. X        } else if (*p == '{') {
  935. X        braces++;
  936. X        } else if (*p == 0) {
  937. X        return p;
  938. X        }
  939. X    }
  940. X    }
  941. X
  942. X    /*
  943. X     * Handle words that don't start with a brace or double-quote.
  944. X     * This code is also invoked if the word starts with a brace or
  945. X     * double-quote and there is garbage after the closing brace or
  946. X     * quote.  This is an error as far as Tcl_Eval is concerned, but
  947. X     * for here the garbage is treated as part of the word.
  948. X     */
  949. X
  950. X    while (*p != 0) {
  951. X    if (*p == '[') {
  952. X        p++;
  953. X        while ((*p != ']') && (*p != 0)) {
  954. X        p = TclWordEnd(p, 1);
  955. X        }
  956. X        if (*p == ']') {
  957. X        p++;
  958. X        }
  959. X    } else if (*p == '\\') {
  960. X        (void) Tcl_Backslash(p, &count);
  961. X        p += count;
  962. X    } else if (*p == '$') {
  963. X        p = VarNameEnd(p);
  964. X    } else if (*p == ';') {
  965. X        /*
  966. X         * Note:  semi-colon terminates a word
  967. X         * and also counts as a word by itself.
  968. X         */
  969. X
  970. X        if (p == start) {
  971. X        p++;
  972. X        }
  973. X        break;
  974. X    } else if (isspace(*p)) {
  975. X        break;
  976. X    } else if ((*p == ']') && nested) {
  977. X        break;
  978. X    } else {
  979. X        p++;
  980. X    }
  981. X    }
  982. X    return p;
  983. X}
  984. X
  985. X/*
  986. X *----------------------------------------------------------------------
  987. X *
  988. X * QuoteEnd --
  989. X *
  990. X *    Given a pointer to a string that obeys the parsing conventions
  991. X *    for quoted things in Tcl, find the end of that quoted thing.
  992. X *    The actual thing may be a quoted argument or a parenthesized
  993. X *    index name.
  994. X *
  995. X * Results:
  996. X *    The return value is a pointer to the character just after the
  997. X *    last one that is part of the quoted string.
  998. X *
  999. X * Side effects:
  1000. X *    None.
  1001. X *
  1002. X *----------------------------------------------------------------------
  1003. X */
  1004. X
  1005. Xstatic char *
  1006. XQuoteEnd(string, term)
  1007. X    char *string;        /* Pointer to character just after opening
  1008. X                 * "quote". */
  1009. X    int term;            /* This character will terminate the
  1010. X                 * quoted string (e.g. '"' or ')'). */
  1011. X{
  1012. X    register char *p = string;
  1013. X    int count;
  1014. X
  1015. X    while ((*p != 0) && (*p != term)) {
  1016. X    if (*p == '\\') {
  1017. X        (void) Tcl_Backslash(p, &count);
  1018. X        p += count;
  1019. X    } else if (*p == '[') {
  1020. X        p++;
  1021. X        while ((*p != ']') && (*p != 0)) {
  1022. X        p = TclWordEnd(p, 1);
  1023. X        }
  1024. X        if (*p == ']') {
  1025. X        p++;
  1026. X        }
  1027. X    } else if (*p == '$') {
  1028. X        p = VarNameEnd(p);
  1029. X    } else {
  1030. X        p++;
  1031. X    }
  1032. X    }
  1033. X    return p;
  1034. X}
  1035. X
  1036. X/*
  1037. X *----------------------------------------------------------------------
  1038. X *
  1039. X * VarNameEnd --
  1040. X *
  1041. X *    Given a pointer to a variable reference using $-notation, find
  1042. X *    the end of the variable name spec.
  1043. X *
  1044. X * Results:
  1045. X *    The return value is a pointer to the character just after the
  1046. X *    last one that is part of the variable name.
  1047. X *
  1048. X * Side effects:
  1049. X *    None.
  1050. X *
  1051. X *----------------------------------------------------------------------
  1052. X */
  1053. X
  1054. Xstatic char *
  1055. XVarNameEnd(string)
  1056. X    char *string;        /* Pointer to dollar-sign character. */
  1057. X{
  1058. X    register char *p = string+1;
  1059. X
  1060. X    if (*p == '{') {
  1061. X    do {
  1062. X        p++;
  1063. X    } while ((*p != '}') && (*p != 0));
  1064. X    } else {
  1065. X    while (isalnum(*p) || (*p == '_')) {
  1066. X        p++;
  1067. X    }
  1068. X    if ((*p == '(') && (p != string+1)) {
  1069. X        p = QuoteEnd(p+1, ')');
  1070. X    }
  1071. X    }
  1072. X    return p;
  1073. X}
  1074. X
  1075. X/*
  1076. X *----------------------------------------------------------------------
  1077. X *
  1078. X * Tcl_ParseVar --
  1079. X *
  1080. X *    Given a string starting with a $ sign, parse off a variable
  1081. X *    name and return its value.
  1082. X *
  1083. X * Results:
  1084. X *    The return value is the contents of the variable given by
  1085. X *    the leading characters of string.  If termPtr isn't NULL,
  1086. X *    *termPtr gets filled in with the address of the character
  1087. X *    just after the last one in the variable specifier.  If the
  1088. X *    variable doesn't exist, then the return value is NULL and
  1089. X *    an error message will be left in interp->result.
  1090. X *
  1091. X * Side effects:
  1092. X *    None.
  1093. X *
  1094. X *----------------------------------------------------------------------
  1095. X */
  1096. X
  1097. Xchar *
  1098. XTcl_ParseVar(interp, string, termPtr)
  1099. X    Tcl_Interp *interp;            /* Context for looking up variable. */
  1100. X    register char *string;        /* String containing variable name.
  1101. X                     * First character must be "$". */
  1102. X    char **termPtr;            /* If non-NULL, points to word to fill
  1103. X                     * in with character just after last
  1104. X                     * one in the variable specifier. */
  1105. X
  1106. X{
  1107. X    char *name1, *name1End, c, *result;
  1108. X    register char *name2;
  1109. X#define NUM_CHARS 200
  1110. X    char copyStorage[NUM_CHARS];
  1111. X    ParseValue pv;
  1112. X
  1113. X    /*
  1114. X     * There are three cases:
  1115. X     * 1. The $ sign is followed by an open curly brace.  Then the variable
  1116. X     *    name is everything up to the next close curly brace, and the
  1117. X     *    variable is a scalar variable.
  1118. X     * 2. The $ sign is not followed by an open curly brace.  Then the
  1119. X     *    variable name is everything up to the next character that isn't
  1120. X     *    a letter, digit, or underscore.  If the following character is an
  1121. X     *    open parenthesis, then the information between parentheses is
  1122. X     *    the array element name, which can include any of the substitutions
  1123. X     *    permissible between quotes.
  1124. X     * 3. The $ sign is followed by something that isn't a letter, digit,
  1125. X     *    or underscore:  in this case, there is no variable name, and "$"
  1126. X     *    is returned.
  1127. X     */
  1128. X
  1129. X    name2 = NULL;
  1130. X    string++;
  1131. X    if (*string == '{') {
  1132. X    string++;
  1133. X    name1 = string;
  1134. X    while (*string != '}') {
  1135. X        if (*string == 0) {
  1136. X        Tcl_SetResult(interp, "missing close-brace for variable name",
  1137. X            TCL_STATIC);
  1138. X        return NULL;
  1139. X        }
  1140. X        string++;
  1141. X    }
  1142. X    name1End = string;
  1143. X    string++;
  1144. X    } else {
  1145. X    name1 = string;
  1146. X    while (isalnum(*string) || (*string == '_')) {
  1147. X        string++;
  1148. X    }
  1149. X    if (string == name1) {
  1150. X        if (termPtr != 0) {
  1151. X        *termPtr = string;
  1152. X        }
  1153. X        return "$";
  1154. X    }
  1155. X    name1End = string;
  1156. X    if (*string == '(') {
  1157. X        char *end;
  1158. X
  1159. X        /*
  1160. X         * Perform substitutions on the array element name, just as
  1161. X         * is done for quotes.
  1162. X         */
  1163. X
  1164. X        pv.buffer = pv.next = copyStorage;
  1165. X        pv.end = copyStorage + NUM_CHARS - 1;
  1166. X        pv.expandProc = TclExpandParseValue;
  1167. X        pv.clientData = (ClientData) NULL;
  1168. X        if (TclParseQuotes(interp, string+1, ')', 0, &end, &pv)
  1169. X            != TCL_OK) {
  1170. X        char msg[100];
  1171. X        sprintf(msg, "\n    (parsing index for array \"%.*s\")",
  1172. X            string-name1, name1);
  1173. X        Tcl_AddErrorInfo(interp, msg);
  1174. X        result = NULL;
  1175. X        name2 = pv.buffer;
  1176. X        goto done;
  1177. X        }
  1178. X        string = end;
  1179. X        name2 = pv.buffer;
  1180. X    }
  1181. X    }
  1182. X    if (termPtr != 0) {
  1183. X    *termPtr = string;
  1184. X    }
  1185. X
  1186. X    c = *name1End;
  1187. X    *name1End = 0;
  1188. X    result = Tcl_GetVar2(interp, name1, name2, TCL_LEAVE_ERR_MSG);
  1189. X    *name1End = c;
  1190. X
  1191. X    done:
  1192. X    if ((name2 != NULL) && (pv.buffer != copyStorage)) {
  1193. X    ckfree(pv.buffer);
  1194. X    }
  1195. X    return result;
  1196. X}
  1197. END_OF_FILE
  1198. if test 32623 -ne `wc -c <'tcl6.1/tclParse.c'`; then
  1199.     echo shar: \"'tcl6.1/tclParse.c'\" unpacked with wrong size!
  1200. fi
  1201. # end of 'tcl6.1/tclParse.c'
  1202. fi
  1203. echo shar: End of archive 24 \(of 33\).
  1204. cp /dev/null ark24isdone
  1205. MISSING=""
  1206. 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
  1207.     if test ! -f ark${I}isdone ; then
  1208.     MISSING="${MISSING} ${I}"
  1209.     fi
  1210. done
  1211. if test "${MISSING}" = "" ; then
  1212.     echo You have unpacked all 33 archives.
  1213.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1214. else
  1215.     echo You still need to unpack the following archives:
  1216.     echo "        " ${MISSING}
  1217. fi
  1218. ##  End of shell archive.
  1219. exit 0
  1220.  
  1221. exit 0 # Just in case...
  1222. -- 
  1223. Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
  1224. Sterling Software, IMD           UUCP:     uunet!sparky!kent
  1225. Phone:    (402) 291-8300         FAX:      (402) 291-4362
  1226. Please send comp.sources.misc-related mail to kent@uunet.uu.net.
  1227.