home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume39 / par131 / part03 / Par131 / par.c < prev    next >
C/C++ Source or Header  |  1993-09-11  |  19KB  |  664 lines

  1. /*********************/
  2. /* par.c             */
  3. /* for Par 1.31      */
  4. /* Copyright 1993 by */
  5. /* Adam M. Costello  */
  6. /*********************/
  7.  
  8. /* This is ANSI C code. */
  9.  
  10.  
  11. #include "charset.h"   /* Also includes "errmsg.h". */
  12. #include "buffer.h"    /* Also includes <stddef.h>. */
  13. #include "reformat.h"
  14.  
  15. #include <stdio.h>
  16. #include <string.h>
  17. #include <stdlib.h>
  18. #include <ctype.h>
  19.  
  20. #undef NULL
  21. #define NULL ((void *) 0)
  22.  
  23. #ifdef DONTFREE
  24. #define free(ptr)
  25. #endif
  26.  
  27.  
  28. const char * const usagemsg =
  29. "\n"
  30. "Usage:\n"
  31. "\n"
  32. "par [help] [version] [B<op><set>] [P<op><set>] [Q<op><set>] [h[<hang>]]\n"
  33. "    [p<prefix>] [s<suffix>] [w<width>] [c[<cap>]] [d[<div>]] [f[<fit>]]\n"
  34. "    [g[<guess>]] [j[<just>]] [l[<last>]] [q[<quote>]] [R[<Report>]]\n"
  35. "    [t[<touch>]]\n"
  36. "\n"
  37. "help       usage message             |"
  38.                                   "Boolean parameters:\n"
  39. "version    version number            |"
  40.                                   "Option:   If 1:\n"
  41. "B<op><set> as <op> is =/+/-,         |"
  42.                                   "c<cap>    all words count as capitalized\n"
  43. "           replace/augment/diminish  |"
  44.                                   "d<div>    indentation delimits paragraphs\n"
  45. "           body chars by <set>       |"
  46.                                   "f<fit>    paragraphs are narrowed to\n"
  47. "P<op><set> ditto for protective chars|"
  48.                                   "          smoothen the right edge\n"
  49. "Q<op><set> ditto for quote chars     |"
  50.                                   "g<guess>  wide sentence breaks preserved\n"
  51. "h<hang>    first <hang> lines of each|"
  52.                                   "j<just>   paragraphs are justified\n"
  53. "           paragraph not searched for|"
  54.                                   "l<last>   last lines treated like others\n"
  55. "           common prefixes & suffixes|"
  56.                                   "q<quote>  vacant lines inserted between\n"
  57. "p<prefix>  prefix length             |"
  58.                                   "          different quote nesting levels\n"
  59. "s<suffix>  suffix length             |"
  60.                                   "R<Report> too-long words cause errors\n"
  61. "w<width>   max output line length    |"
  62.                                   "t<touch>  suffixes are moved left\n"
  63. "\n"
  64. "See par.doc or par.1 (the man page) for more information.\n"
  65. ;
  66.  
  67.  
  68. static int digtoint(char c)
  69.  
  70. /* Returns the value represented by the digit c, or -1 if c is not a digit. */
  71. {
  72.   const char *p, * const digits = "0123456789";
  73.  
  74.   if (!c) return -1;
  75.   p = strchr(digits,c);
  76.   return  p  ?  p - digits  :  -1;
  77.  
  78.   /* We can't simply return c - '0' because this is ANSI C code,  */
  79.   /* so it has to work for any character set, not just ones which */
  80.   /* put the digits together in order.  Also, an array that could */
  81.   /* be referenced as digtoint[c] might be bad because there's no */
  82.   /* upper limit on CHAR_MAX.                                     */
  83. }
  84.  
  85.  
  86. static int strtoudec(const char *s, int *pn)
  87.  
  88. /* Converts the longest prefix of string s consisting of decimal   */
  89. /* digits to an integer, which is stored in *pn.  Normally returns */
  90. /* 1.  If *s is not a digit, then *pn is not changed, but 1 is     */
  91. /* still returned.  If the integer represented is greater than     */
  92. /* 9999, then *pn is not changed and 0 is returned.                */
  93. {
  94.   int n = 0, d;
  95.  
  96.   d = digtoint(*s);
  97.   if (d < 0) return 1;
  98.  
  99.   do {
  100.     if (n >= 1000) return 0;
  101.     n = 10 * n + d;
  102.     d = digtoint(*++s);
  103.   } while (d >= 0);
  104.  
  105.   *pn = n;
  106.  
  107.   return 1;
  108. }
  109.  
  110.  
  111. static void parsearg(
  112.   const char *arg, int *phelp, int *pversion, charset *bodychars,
  113.   charset *protectchars, charset *quotechars, int *phang, int *pprefix,
  114.   int *psuffix, int *pwidth, int *pcap, int *pdiv, int *pfit, int *pguess,
  115.   int *pjust, int *plast, int *pquote, int *pReport, int *ptouch,
  116.   errmsg_t errmsg
  117. )
  118. /* Parses the command line argument in *arg, setting the objects pointed to */
  119. /* by the other pointers as appropriate. *phelp and *pversion are boolean   */
  120. /* flags indicating whether the help and version options were supplied.     */
  121. {
  122.   const char *savearg = arg;
  123.   charset *chars, *change;
  124.   char oc;
  125.   int n;
  126.  
  127.   *errmsg = '\0';
  128.  
  129.   if (*arg == '-') ++arg;
  130.  
  131.   if (!strcmp(arg, "help")) {
  132.     *phelp = 1;
  133.     return;
  134.   }
  135.  
  136.   if (!strcmp(arg, "version")) {
  137.     *pversion = 1;
  138.     return;
  139.   }
  140.  
  141.   if (*arg == 'B' || *arg == 'P' || *arg == 'Q' ) {
  142.     chars =  *arg == 'B'  ?  bodychars    :
  143.              *arg == 'P'  ?  protectchars :
  144.           /* *arg == 'Q' */  quotechars   ;
  145.     ++arg;
  146.     if (*arg != '='  &&  *arg != '+'  &&  *arg != '-') goto badarg;
  147.     change = parsecharset(arg + 1, errmsg);
  148.     if (change) {
  149.       if      (*arg == '=')   csswap(chars,change);
  150.       else if (*arg == '+')   csadd(chars,change,errmsg);
  151.       else  /* *arg == '-' */ csremove(chars,change,errmsg);
  152.       freecharset(change);
  153.     }
  154.     return;
  155.   }
  156.  
  157.   if (isdigit(*arg)) {
  158.     if (!strtoudec(arg, &n)) goto badarg;
  159.     if (n <= 8) *pprefix = n;
  160.     else *pwidth = n;
  161.   }
  162.  
  163.   for (;;) {
  164.     while (isdigit(*arg)) ++arg;
  165.     oc = *arg;
  166.     if (!oc) break;
  167.     n = 1;
  168.     if (!strtoudec(++arg, &n)) goto badarg;
  169.     if (oc == 'h' || oc == 'p' || oc == 's' || oc == 'w') {
  170.       if (oc == 'h') *phang = n;
  171.       else {
  172.         if (!isdigit(*arg)) goto badarg;
  173.         if      (oc == 'w')   *pwidth  = n;
  174.         else if (oc == 'p')   *pprefix = n;
  175.         else  /* oc == 's' */ *psuffix = n;
  176.       }
  177.     }
  178.     else {
  179.       if (n > 1) goto badarg;
  180.       if      (oc == 'c') *pcap    = n;
  181.       else if (oc == 'd') *pdiv    = n;
  182.       else if (oc == 'f') *pfit    = n;
  183.       else if (oc == 'g') *pguess  = n;
  184.       else if (oc == 'j') *pjust   = n;
  185.       else if (oc == 'l') *plast   = n;
  186.       else if (oc == 'q') *pquote  = n;
  187.       else if (oc == 'R') *pReport = n;
  188.       else if (oc == 't') *ptouch  = n;
  189.       else goto badarg;
  190.     }
  191.   }
  192.  
  193.   return;
  194.  
  195. badarg:
  196.  
  197.   sprintf(errmsg, "Bad argument: %.*s\n", errmsg_size - 16, savearg);
  198.   *phelp = 1;
  199. }
  200.  
  201.  
  202. static char **readlines(const charset *protectchars,
  203.                         const charset *quotechars, int quote, errmsg_t errmsg)
  204.  
  205. /* Reads lines from stdin until EOF, or until a line beginning with a */
  206. /* protective character is encountered (in which case the protective  */
  207. /* character is pushed back onto the input stream), or until a blank  */
  208. /* line is encountered (in which case the newline is pushed back onto */
  209. /* the input stream).  Returns a NULL-terminated array of pointers to */
  210. /* individual lines, stripped of their newline characters.  Every NUL */
  211. /* character is stripped, and every white character is changed to a   */
  212. /* space unless it is a newline.  If quote is 1, vacant lines will be */
  213. /* inserted as described for the q option in par.doc.  Returns NULL   */
  214. /* on failure.                                                        */
  215. {
  216.   buffer *cbuf = NULL, *lbuf = NULL;
  217.   int c, empty, blank, nonquote, oldnonquote = 0, qplen;
  218.   char ch, *ln = NULL, nullchar = '\0', *nullline = NULL, *qpstart, *qpend,
  219.        *oldqpstart = &nullchar, *oldqpend = &nullchar, *p, *op, *vln = NULL,
  220.        **lines = NULL;
  221.  
  222.   *errmsg = '\0';
  223.  
  224.   cbuf = newbuffer(sizeof (char), errmsg);
  225.   if (*errmsg) goto rlcleanup;
  226.   lbuf = newbuffer(sizeof (char *), errmsg);
  227.   if (*errmsg) goto rlcleanup;
  228.  
  229.   for (empty = blank = 1;  ; ) {
  230.     c = getchar();
  231.     if (c == EOF) break;
  232.     if (c == '\n') {
  233.       if (blank) {
  234.         ungetc(c,stdin);
  235.         break;
  236.       }
  237.       additem(cbuf, &nullchar, errmsg);
  238.       if (*errmsg) goto rlcleanup;
  239.       ln = copyitems(cbuf,errmsg);
  240.       if (*errmsg) goto rlcleanup;
  241.       if (quote) {
  242.         qpstart = ln;
  243.         for (qpend = qpstart;
  244.              *qpend && csmember(*qpend, quotechars);
  245.              ++qpend);
  246.         nonquote = *qpend != '\0';
  247.         while (qpend > qpstart && qpend[-1] == ' ') --qpend;
  248.         for (p = qpstart, op = oldqpstart;
  249.              p < qpend && op < oldqpend && *p == *op;
  250.              ++p, ++op);
  251.         if (   (p < qpend && op == oldqpend  ||  p == qpend && op < oldqpend)
  252.             && nonquote && oldnonquote) {
  253.           qplen = p - qpstart;
  254.           vln = malloc((qplen + 1) * sizeof (char));
  255.           if (!vln) {
  256.             strcpy(errmsg,outofmem);
  257.             goto rlcleanup;
  258.           }
  259.           strncpy(vln,qpstart,qplen);
  260.           vln[qplen] = '\0';
  261.           additem(lbuf, &vln, errmsg);
  262.           if (*errmsg) goto rlcleanup;
  263.           vln = NULL;
  264.         }
  265.         oldqpstart = qpstart;
  266.         oldqpend = qpend;
  267.         oldnonquote = nonquote;
  268.       }
  269.       additem(lbuf, &ln, errmsg);
  270.       if (*errmsg) goto rlcleanup;
  271.       ln = NULL;
  272.       clearbuffer(cbuf);
  273.       empty = blank = 1;
  274.     }
  275.     else {
  276.       if (empty) {
  277.         if (csmember((char) c, protectchars)) {
  278.           ungetc(c,stdin);
  279.           break;
  280.         }
  281.         empty = 0;
  282.       }
  283.       if (!c) continue;
  284.       if (isspace(c)) c = ' ';
  285.       else blank = 0;
  286.       ch = c;
  287.       additem(cbuf, &ch, errmsg);
  288.       if (*errmsg) goto rlcleanup;
  289.     }
  290.   }
  291.  
  292.   if (!blank) {
  293.     additem(cbuf, &nullchar, errmsg);
  294.     if (*errmsg) goto rlcleanup;
  295.     ln = copyitems(cbuf,errmsg);
  296.     if (*errmsg) goto rlcleanup;
  297.     additem(lbuf, &ln, errmsg);
  298.     if (*errmsg) goto rlcleanup;
  299.     ln = NULL;
  300.   }
  301.  
  302.   additem(lbuf, &nullline, errmsg);
  303.   if (*errmsg) goto rlcleanup;
  304.   lines = copyitems(lbuf,errmsg);
  305.  
  306. rlcleanup:
  307.  
  308.   if (cbuf) freebuffer(cbuf);
  309.   if (lbuf) {
  310.     if (!lines)
  311.       for (;;) {
  312.         lines = nextitem(lbuf);
  313.         if (!lines) break;
  314.         free(*lines);
  315.       }
  316.     freebuffer(lbuf);
  317.   }
  318.   if (ln) free(ln);
  319.   if (vln) free(vln);
  320.  
  321.   return lines;
  322. }
  323.  
  324.  
  325. static void compresuflen(
  326.   const char * const *lines, const char * const *endline,
  327.   const charset *bodychars, int pre, int suf, int *ppre, int *psuf
  328. )
  329. /* lines is an array of strings, up to but not including endline.  */
  330. /* Writes into *ppre and *psuf the comprelen and comsuflen of the  */
  331. /* lines in lines.  Assumes that they have already been determined */
  332. /* to be at least pre and suf. endline must not equal lines.       */
  333. {
  334.   const char *start, *end, * const *line, *p1, *p2, *start2;
  335.  
  336.   start = *lines;
  337.   for (end = start + pre;  *end && !csmember(*end, bodychars);  ++end);
  338.   for (line = lines + 1;  line < endline;  ++line) {
  339.     for (p1 = start + pre, p2 = *line + pre;
  340.          p1 < end && *p1 == *p2;
  341.          ++p1, ++p2);
  342.     end = p1;
  343.   }
  344.   *ppre = end - start;
  345.  
  346.   start2 = *lines + *ppre;
  347.   for (end = start2;  *end;  ++end);
  348.   for (start = end - suf;
  349.        start > start2 && !csmember(start[-1], bodychars);
  350.        --start);
  351.   for (line = lines + 1;  line < endline;  ++line) {
  352.     start2 = *line + *ppre;
  353.     for (p2 = start2;  *p2;  ++p2);
  354.     for (p1 = end - suf, p2 -= suf;
  355.          p1 > start && p2 > start2 && p1[-1] == p2[-1];
  356.          --p1, --p2);
  357.     start = p1;
  358.   }
  359.   while (end - start >= 2 && *start == ' ' && start[1] == ' ') ++start;
  360.   *psuf = end - start;
  361. }
  362.  
  363.  
  364. static void delimit(
  365.   const char * const *lines, const char * const *endline,
  366.   const charset *bodychars, int div, int pre, int suf, char *tags
  367. )
  368. /* lines is an array of strings, up to but not including endline.     */
  369. /* Sets each character in the parallel array tags to 'f', 'p', or     */
  370. /* 'v' according to whether the corresponding line in lines is the    */
  371. /* first line of a paragraph, some other line in a paragraph, or a    */
  372. /* vacant line, respectively, depending on the values of bodychars    */
  373. /* and div, according to "par.doc".  It is assumed that the comprelen */
  374. /* and comsuflen of the lines in lines have already been determined   */
  375. /* to be at least pre and suf, respectively.                          */
  376. {
  377.   const char * const *line, *end, *p, * const *nextline;
  378.   char *tag, *nexttag;
  379.   int anyvacant = 0, status;
  380.  
  381.   if (endline == lines) return;
  382.  
  383.   if (endline == lines + 1) {
  384.     *tags = 'f';
  385.     return;
  386.   }
  387.  
  388.   compresuflen(lines, endline, bodychars, pre, suf, &pre, &suf);
  389.  
  390.   line = lines;
  391.   tag = tags;
  392.   do {
  393.     *tag = 'v';
  394.     for (end = *line;  *end;  ++end);
  395.     end -= suf;
  396.     for (p = *line + pre;  p < end;  ++p)
  397.       if (*p != ' ') {
  398.         *tag = 'p';
  399.         break;
  400.       }
  401.     if (*tag == 'v') anyvacant = 1;
  402.     ++line;
  403.     ++tag;
  404.   } while (line < endline);
  405.  
  406.   if (anyvacant) {
  407.     line = lines;
  408.     tag = tags;
  409.     do {
  410.       if (*tag == 'v') {
  411.         ++line;
  412.         ++tag;
  413.         continue;
  414.       }
  415.  
  416.       for (nextline = line + 1, nexttag = tag + 1;
  417.            nextline < endline && *nexttag == 'p';
  418.            ++nextline, ++nexttag);
  419.  
  420.       delimit(line,nextline,bodychars,div,pre,suf,tag);
  421.  
  422.       line = nextline;
  423.       tag = nexttag;
  424.     } while (line < endline);
  425.  
  426.     return;
  427.   }
  428.  
  429.   if (!div) {
  430.     *tags = 'f';
  431.     return;
  432.   }
  433.  
  434.   line = lines;
  435.   tag = tags;
  436.   status = ((*lines)[pre] == ' ');
  437.   do {
  438.     if (((*line)[pre] == ' ') == status)
  439.       *tag = 'f';
  440.     ++line;
  441.     ++tag;
  442.   } while (line < endline);
  443. }
  444.  
  445.  
  446. static void setaffixes(
  447.   const char * const *inlines, const char * const *endline,
  448.   const charset *bodychars, const charset *quotechars,
  449.   int hang, int quote, int *pprefix, int *psuffix
  450. )
  451. /* inlines is an array of strings, up to but not including   */
  452. /* endline.  If either of *pprefix, *psuffix is less than 0, */
  453. /* sets it to a default value based on inlines, bodychars,   */
  454. /* quotechars, hang, and quote, according to "par.doc".      */
  455. {
  456.   int numin, pre, suf;
  457.   const char *start, *p;
  458.  
  459.   numin = endline - inlines;
  460.  
  461.   if ((*pprefix < 0 || *psuffix < 0) && numin > hang + 1)
  462.     compresuflen(inlines + hang, endline, bodychars, 0, 0, &pre, &suf);
  463.  
  464.   if (*pprefix < 0)
  465.     if (quote && numin == hang + 1) {
  466.       start = inlines[hang];
  467.       for (p = start;  *p && csmember(*p, quotechars);  ++p);
  468.       *pprefix = p - start;
  469.     }
  470.     else *pprefix = numin > hang + 1  ?  pre  :  0;
  471.  
  472.   if (*psuffix < 0)
  473.     *psuffix = numin > hang + 1  ?  suf  :  0;
  474. }
  475.  
  476.  
  477. static void freelines(char **lines)
  478. /* Frees the elements of lines, and lines itself. */
  479. /* lines is a NULL-terminated array of strings.   */
  480. {
  481.   char **line;
  482.  
  483.   for (line = lines;  *line;  ++line)
  484.     free(*line);
  485.  
  486.   free(lines);
  487. }
  488.  
  489.  
  490. main(int argc, const char * const *argv)
  491. {
  492.   int help = 0, version = 0, hang = 0, prefix = -1, suffix = -1, width = 72,
  493.       cap = 0, div = 0, fit = 0, guess = 0, just = 0, last = 0, quote = 0,
  494.       Report = 0, touch = -1, prefixbak, suffixbak, c;
  495.   charset *bodychars = NULL, *protectchars = NULL, *quotechars = NULL;
  496.   char *parinit = NULL, *arg, **inlines = NULL, **endline,
  497.        *tags = NULL, **firstline, *firsttag, *end, **nextline, *nexttag,
  498.        **outlines = NULL, **line;
  499.   const char *env, * const whitechars = " \f\n\r\t\v";
  500.   errmsg_t errmsg = { '\0' };
  501.  
  502. /* Process environment variables: */
  503.  
  504.   env = getenv("PARBODY");
  505.   if (!env) env = "";
  506.   bodychars = parsecharset(env,errmsg);
  507.   if (*errmsg) {
  508.     help = 1;
  509.     goto parcleanup;
  510.   }
  511.  
  512.   env = getenv("PARPROTECT");
  513.   if (!env) env = "";
  514.   protectchars = parsecharset(env,errmsg);
  515.   if (*errmsg) {
  516.     help = 1;
  517.     goto parcleanup;
  518.   }
  519.  
  520.   env = getenv("PARQUOTE");
  521.   if (!env) env = "> ";
  522.   quotechars = parsecharset(env,errmsg);
  523.   if (*errmsg) {
  524.     help = 1;
  525.     goto parcleanup;
  526.   }
  527.  
  528.   env = getenv("PARINIT");
  529.   if (env) {
  530.     parinit = malloc((strlen(env) + 1) * sizeof (char));
  531.     if (!parinit) {
  532.       strcpy(errmsg,outofmem);
  533.       goto parcleanup;
  534.     }
  535.     strcpy(parinit,env);
  536.     arg = strtok(parinit,whitechars);
  537.     while (arg) {
  538.       parsearg(arg, &help, &version, bodychars, protectchars,
  539.                quotechars, &hang, &prefix, &suffix, &width, &cap, &div,
  540.                &fit, &guess, &just, &last, "e, &Report, &touch, errmsg);
  541.       if (*errmsg || help || version) goto parcleanup;
  542.       arg = strtok(NULL,whitechars);
  543.     }
  544.     free(parinit);
  545.     parinit = NULL;
  546.   }
  547.  
  548. /* Process command line arguments: */
  549.  
  550.   while (*++argv) {
  551.     parsearg(*argv, &help, &version, bodychars, protectchars,
  552.              quotechars, &hang, &prefix, &suffix, &width, &cap, &div,
  553.              &fit, &guess, &just, &last, "e, &Report, &touch, errmsg);
  554.     if (*errmsg || help || version) goto parcleanup;
  555.   }
  556.  
  557.   if (touch < 0) touch = fit || last;
  558.   prefixbak = prefix;
  559.   suffixbak = suffix;
  560.  
  561. /* Main loop: */
  562.  
  563.   for (;;) {
  564.     for (;;) {
  565.       c = getchar();
  566.       if (csmember((char) c, protectchars))
  567.         while (c != '\n' && c != EOF) {
  568.           putchar(c);
  569.           c = getchar();
  570.         }
  571.       if (c != '\n') break;
  572.       putchar(c);
  573.     }
  574.     if (c == EOF) break;
  575.     ungetc(c,stdin);
  576.  
  577.     inlines = readlines(protectchars,quotechars,quote,errmsg);
  578.     if (*errmsg) goto parcleanup;
  579.  
  580.     for (endline = inlines;  *endline;  ++endline);
  581.     if (endline == inlines) {
  582.       free(inlines);
  583.       inlines = NULL;
  584.       continue;
  585.     }
  586.  
  587.     tags = malloc((endline - inlines) * sizeof(char));
  588.     if (!tags) {
  589.       strcpy(errmsg,outofmem);
  590.       goto parcleanup;
  591.     }
  592.  
  593.     delimit((const char * const *) inlines,
  594.             (const char * const *) endline, bodychars, div, 0, 0, tags);
  595.  
  596.     firstline = inlines;
  597.     firsttag = tags;
  598.     do {
  599.       if (*firsttag == 'v') {
  600.         for (end = *firstline;  *end;  ++end);
  601.         while (end > *firstline && end[-1] == ' ') --end;
  602.         *end = '\0';
  603.         puts(*firstline);
  604.         ++firsttag;
  605.         ++firstline;
  606.         continue;
  607.       }
  608.  
  609.       for (nexttag = firsttag + 1, nextline = firstline + 1;
  610.            nextline < endline && *nexttag == 'p';
  611.            ++nexttag, ++nextline);
  612.  
  613.       prefix = prefixbak;
  614.       suffix = suffixbak;
  615.       setaffixes((const char * const *) firstline,
  616.                  (const char * const *) nextline,
  617.                  bodychars, quotechars, hang, quote, &prefix, &suffix);
  618.       if (width <= prefix + suffix) {
  619.         sprintf(errmsg,
  620.                 "<width> (%d) <= <prefix> (%d) + <suffix> (%d)\n",
  621.                 width, prefix, suffix);
  622.         goto parcleanup;
  623.       }
  624.  
  625.       outlines =
  626.         reformat((const char * const *) firstline,
  627.                  (const char * const *) nextline, hang, prefix, suffix,
  628.                   width, cap, fit, guess, just, last, Report, touch, errmsg);
  629.       if (*errmsg) goto parcleanup;
  630.  
  631.       for (line = outlines;  *line;  ++line)
  632.         puts(*line);
  633.  
  634.       freelines(outlines);
  635.       outlines = NULL;
  636.  
  637.       firsttag = nexttag;
  638.       firstline = nextline;
  639.     } while (firstline < endline);
  640.  
  641.     free(tags);
  642.     tags = NULL;
  643.  
  644.     freelines(inlines);
  645.     inlines = NULL;
  646.   }
  647.  
  648. parcleanup:
  649.  
  650.   if (bodychars) freecharset(bodychars);
  651.   if (protectchars) freecharset(protectchars);
  652.   if (quotechars) freecharset(quotechars);
  653.   if (parinit) free(parinit);
  654.   if (inlines) freelines(inlines);
  655.   if (tags) free(tags);
  656.   if (outlines) freelines(outlines);
  657.  
  658.   if (*errmsg) printf("par error:\n%.*s", errmsg_size, errmsg);
  659.   if (version) puts("par 1.31");
  660.   if (help)    fputs(usagemsg,stdout);
  661.  
  662.   exit(*errmsg ? EXIT_FAILURE : EXIT_SUCCESS);
  663. }
  664.