1 /*      $NetBSD: fparseln.c,v 1.9 1999/09/20 04:48:06 lukem Exp $       */
   2 
   3 /*
   4  * Copyright (c) 1997 Christos Zoulas.  All rights reserved.
   5  *
   6  * Redistribution and use in source and binary forms, with or without
   7  * modification, are permitted provided that the following conditions
   8  * are met:
   9  * 1. Redistributions of source code must retain the above copyright
  10  *    notice, this list of conditions and the following disclaimer.
  11  * 2. Redistributions in binary form must reproduce the above copyright
  12  *    notice, this list of conditions and the following disclaimer in the
  13  *    documentation and/or other materials provided with the distribution.
  14  * 3. All advertising materials mentioning features or use of this software
  15  *    must display the following acknowledgement:
  16  *      This product includes software developed by Christos Zoulas.
  17  * 4. The name of the author may not be used to endorse or promote products
  18  *    derived from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 #include <stdio.h>
  33 #include <string.h>
  34 #include <stdlib.h>
  35 #include <compat.h>
  36 
  37 static int isescaped(const char *, const char *, int);
  38 
  39 /*
  40  * isescaped():
  41  *      Return true if the character in *p that belongs to a string
  42  *      that starts in *sp, is escaped by the escape character esc.
  43  */
  44 static int
  45 isescaped(const char *sp, const char *p, int esc)
  46 {
  47         const char      *cp;
  48         size_t          ne;
  49 
  50         /* No escape character */
  51         if (esc == '\0')
  52                 return (1);
  53 
  54         /* Count the number of escape characters that precede ours */
  55         for (ne = 0, cp = p; --cp >= sp && *cp == esc; ne++)
  56                 continue;
  57 
  58         /* Return true if odd number of escape characters */
  59         return ((ne & 1) != 0);
  60 }
  61 
  62 
  63 /*
  64  * fparseln():
  65  *      Read a line from a file parsing continuations ending in \
  66  *      and eliminating trailing newlines, or comments starting with
  67  *      the comment char.
  68  */
  69 char *
  70 fparseln(FILE *fp, size_t *size, size_t *lineno, const char str[3], int flags)
  71 {
  72         static const char dstr[3] = { '\\', '\\', '#' };
  73 
  74         size_t  s, len;
  75         char   *buf;
  76         char   *ptr, *cp;
  77         int     cnt;
  78         char    esc, con, nl, com;
  79 
  80         len = 0;
  81         buf = NULL;
  82         cnt = 1;
  83 
  84         if (str == NULL)
  85                 str = dstr;
  86 
  87         esc = str[0];
  88         con = str[1];
  89         com = str[2];
  90         /*
  91          * XXX: it would be cool to be able to specify the newline character,
  92          * but unfortunately, fgetln does not let us
  93          */
  94         nl  = '\n';
  95 
  96         while (cnt) {
  97                 cnt = 0;
  98 
  99                 if (lineno)
 100                         (*lineno)++;
 101 
 102                 if ((ptr = fgetln(fp, &s)) == NULL)
 103                         break;
 104 
 105                 if (s && com) {         /* Check and eliminate comments */
 106                         for (cp = ptr; cp < ptr + s; cp++)
 107                                 if (*cp == com && !isescaped(ptr, cp, esc)) {
 108                                         s = cp - ptr;
 109                                         cnt = s == 0 && buf == NULL;
 110                                         break;
 111                                 }
 112                 }
 113 
 114                 if (s && nl) {          /* Check and eliminate newlines */
 115                         cp = &ptr[s - 1];
 116 
 117                         if (*cp == nl)
 118                                 s--;    /* forget newline */
 119                 }
 120 
 121                 if (s && con) {         /* Check and eliminate continuations */
 122                         cp = &ptr[s - 1];
 123 
 124                         if (*cp == con && !isescaped(ptr, cp, esc)) {
 125                                 s--;    /* forget escape */
 126                                 cnt = 1;
 127                         }
 128                 }
 129 
 130                 if (s == 0 && buf != NULL)
 131                         continue;
 132 
 133                 if ((cp = realloc(buf, len + s + 1)) == NULL) {
 134                         free(buf);
 135                         return (NULL);
 136                 }
 137                 buf = cp;
 138 
 139                 (void) memcpy(buf + len, ptr, s);
 140                 len += s;
 141                 buf[len] = '\0';
 142         }
 143 
 144         if ((flags & FPARSELN_UNESCALL) != 0 && esc && buf != NULL &&
 145             strchr(buf, esc) != NULL) {
 146                 ptr = cp = buf;
 147                 while (cp[0] != '\0') {
 148                         int skipesc;
 149 
 150                         while (cp[0] != '\0' && cp[0] != esc)
 151                                 *ptr++ = *cp++;
 152                         if (cp[0] == '\0' || cp[1] == '\0')
 153                                 break;
 154 
 155                         skipesc = 0;
 156                         if (cp[1] == com)
 157                                 skipesc += (flags & FPARSELN_UNESCCOMM);
 158                         if (cp[1] == con)
 159                                 skipesc += (flags & FPARSELN_UNESCCONT);
 160                         if (cp[1] == esc)
 161                                 skipesc += (flags & FPARSELN_UNESCESC);
 162                         if (cp[1] != com && cp[1] != con && cp[1] != esc)
 163                                 skipesc = (flags & FPARSELN_UNESCREST);
 164 
 165                         if (skipesc)
 166                                 cp++;
 167                         else
 168                                 *ptr++ = *cp++;
 169                         *ptr++ = *cp++;
 170                 }
 171                 *ptr = '\0';
 172                 len = strlen(buf);
 173         }
 174 
 175         if (size) {
 176                 *size = len;
 177         }
 178         return (buf);
 179 }
 180 
 181 #ifdef TEST
 182 
 183 int
 184 main(int argc, char *argv[])
 185 {
 186         char   *ptr;
 187         size_t  size, line;
 188 
 189         line = 0;
 190         while ((ptr = fparseln(stdin, &size, &line, NULL,
 191             FPARSELN_UNESCALL)) != NULL) {
 192                 printf("line %d (%d) |%s|\n", line, size, ptr);
 193         }
 194         return (0);
 195 }
 196 
 197 /*
 198  * # This is a test
 199  * line 1
 200  * line 2 \
 201  * line 3 # Comment
 202  * line 4 \# Not comment \\\\
 203  *
 204  * # And a comment \
 205  * line 5 \\\
 206  * line 6
 207  *
 208  */
 209 
 210 #endif /* TEST */