xref: /freebsd/lib/libutil/fparseln.c (revision 8af1452cf8dd1b33da881f84e445d5981eaa9613)
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 <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 __RCSID("$FreeBSD$");
35 #endif
36 
37 #include <sys/types.h>
38 #include <assert.h>
39 #include <errno.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <libutil.h>
44 
45 static int isescaped __P((const char *, const char *, int));
46 
47 /* isescaped():
48  *	Return true if the character in *p that belongs to a string
49  *	that starts in *sp, is escaped by the escape character esc.
50  */
51 static int
52 isescaped(sp, p, esc)
53 	const char *sp, *p;
54 	int esc;
55 {
56 	const char     *cp;
57 	size_t		ne;
58 
59 #if 0
60 	_DIAGASSERT(sp != NULL);
61 	_DIAGASSERT(p != NULL);
62 #endif
63 
64 	/* No escape character */
65 	if (esc == '\0')
66 		return 1;
67 
68 	/* Count the number of escape characters that precede ours */
69 	for (ne = 0, cp = p; --cp >= sp && *cp == esc; ne++)
70 		continue;
71 
72 	/* Return true if odd number of escape characters */
73 	return (ne & 1) != 0;
74 }
75 
76 
77 /* fparseln():
78  *	Read a line from a file parsing continuations ending in \
79  *	and eliminating trailing newlines, or comments starting with
80  *	the comment char.
81  */
82 char *
83 fparseln(fp, size, lineno, str, flags)
84 	FILE		*fp;
85 	size_t		*size;
86 	size_t		*lineno;
87 	const char	 str[3];
88 	int		 flags;
89 {
90 	static const char dstr[3] = { '\\', '\\', '#' };
91 
92 	size_t	s, len;
93 	char   *buf;
94 	char   *ptr, *cp;
95 	int	cnt;
96 	char	esc, con, nl, com;
97 
98 #if 0
99 	_DIAGASSERT(fp != NULL);
100 #endif
101 
102 	len = 0;
103 	buf = NULL;
104 	cnt = 1;
105 
106 	if (str == NULL)
107 		str = dstr;
108 
109 	esc = str[0];
110 	con = str[1];
111 	com = str[2];
112 	/*
113 	 * XXX: it would be cool to be able to specify the newline character,
114 	 * but unfortunately, fgetln does not let us
115 	 */
116 	nl  = '\n';
117 
118 	while (cnt) {
119 		cnt = 0;
120 
121 		if (lineno)
122 			(*lineno)++;
123 
124 		if ((ptr = fgetln(fp, &s)) == NULL)
125 			break;
126 
127 		if (s && com) {		/* Check and eliminate comments */
128 			for (cp = ptr; cp < ptr + s; cp++)
129 				if (*cp == com && !isescaped(ptr, cp, esc)) {
130 					s = cp - ptr;
131 					cnt = s == 0 && buf == NULL;
132 					break;
133 				}
134 		}
135 
136 		if (s && nl) { 		/* Check and eliminate newlines */
137 			cp = &ptr[s - 1];
138 
139 			if (*cp == nl)
140 				s--;	/* forget newline */
141 		}
142 
143 		if (s && con) {		/* Check and eliminate continuations */
144 			cp = &ptr[s - 1];
145 
146 			if (*cp == con && !isescaped(ptr, cp, esc)) {
147 				s--;	/* forget escape */
148 				cnt = 1;
149 			}
150 		}
151 
152 		if (s == 0 && buf != NULL)
153 			continue;
154 
155 		if ((cp = realloc(buf, len + s + 1)) == NULL) {
156 			free(buf);
157 			return NULL;
158 		}
159 		buf = cp;
160 
161 		(void) memcpy(buf + len, ptr, s);
162 		len += s;
163 		buf[len] = '\0';
164 	}
165 
166 	if ((flags & FPARSELN_UNESCALL) != 0 && esc && buf != NULL &&
167 	    strchr(buf, esc) != NULL) {
168 		ptr = cp = buf;
169 		while (cp[0] != '\0') {
170 			int skipesc;
171 
172 			while (cp[0] != '\0' && cp[0] != esc)
173 				*ptr++ = *cp++;
174 			if (cp[0] == '\0' || cp[1] == '\0')
175 				break;
176 
177 			skipesc = 0;
178 			if (cp[1] == com)
179 				skipesc += (flags & FPARSELN_UNESCCOMM);
180 			if (cp[1] == con)
181 				skipesc += (flags & FPARSELN_UNESCCONT);
182 			if (cp[1] == esc)
183 				skipesc += (flags & FPARSELN_UNESCESC);
184 			if (cp[1] != com && cp[1] != con && cp[1] != esc)
185 				skipesc = (flags & FPARSELN_UNESCREST);
186 
187 			if (skipesc)
188 				cp++;
189 			else
190 				*ptr++ = *cp++;
191 			*ptr++ = *cp++;
192 		}
193 		*ptr = '\0';
194 		len = strlen(buf);
195 	}
196 
197 	if (size)
198 		*size = len;
199 	return buf;
200 }
201 
202 #ifdef TEST
203 
204 int main __P((int, char **));
205 
206 int
207 main(argc, argv)
208 	int argc;
209 	char **argv;
210 {
211 	char   *ptr;
212 	size_t	size, line;
213 
214 	line = 0;
215 	while ((ptr = fparseln(stdin, &size, &line, NULL,
216 	    FPARSELN_UNESCALL)) != NULL)
217 		printf("line %d (%d) |%s|\n", line, size, ptr);
218 	return 0;
219 }
220 
221 /*
222 
223 # This is a test
224 line 1
225 line 2 \
226 line 3 # Comment
227 line 4 \# Not comment \\\\
228 
229 # And a comment \
230 line 5 \\\
231 line 6
232 
233 */
234 
235 #endif /* TEST */
236