xref: /openbsd-src/libexec/tradcpp/directive.c (revision b8851fcc53cbe24fd20b090f26dd149e353f6174)
1 /*-
2  * Copyright (c) 2010, 2013 The NetBSD Foundation, Inc.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to The NetBSD Foundation
6  * by David A. Holland.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <assert.h>
31 #include <stdbool.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include "utils.h"
36 #include "mode.h"
37 #include "place.h"
38 #include "files.h"
39 #include "directive.h"
40 #include "macro.h"
41 #include "eval.h"
42 #include "output.h"
43 
44 struct ifstate {
45 	struct ifstate *prev;
46 	struct place startplace;
47 	bool curtrue;
48 	bool evertrue;
49 	bool seenelse;
50 };
51 
52 static struct ifstate *ifstate;
53 
54 ////////////////////////////////////////////////////////////
55 // common parsing bits
56 
57 static
58 void
59 uncomment(char *buf)
60 {
61 	char *s, *t, *u = NULL;
62 	bool incomment = false;
63 	bool inesc = false;
64 	bool inquote = false;
65 	char quote = '\0';
66 
67 	for (s = t = buf; *s; s++) {
68 		if (incomment) {
69 			if (s[0] == '*' && s[1] == '/') {
70 				s++;
71 				incomment = false;
72 			}
73 		} else {
74 			if (!inquote && s[0] == '/' && s[1] == '*') {
75 				incomment = true;
76 			} else {
77 				if (inesc) {
78 					inesc = false;
79 				} else if (s[0] == '\\') {
80 					inesc = true;
81 				} else if (!inquote &&
82 					   (s[0] == '"' || s[0] == '\'')) {
83 					inquote = true;
84 					quote = s[0];
85 				} else if (inquote && s[0] == quote) {
86 					inquote = false;
87 				}
88 
89 				if (t != s) {
90 					*t = *s;
91 				}
92 				if (!strchr(ws, *t)) {
93 					u = t;
94 				}
95 				t++;
96 			}
97 		}
98 	}
99 	if (u) {
100 		/* end string after last non-whitespace char */
101 		u[1] = '\0';
102 	} else {
103 		*t = '\0';
104 	}
105 }
106 
107 static
108 void
109 oneword(const char *what, struct place *p2, char *line)
110 {
111 	size_t pos;
112 
113 	pos = strcspn(line, ws);
114 	if (line[pos] != '\0') {
115 		p2->column += pos;
116 		complain(p2, "Garbage after %s argument", what);
117 		complain_fail();
118 		line[pos] = '\0';
119 	}
120 }
121 
122 ////////////////////////////////////////////////////////////
123 // if handling
124 
125 static
126 struct ifstate *
127 ifstate_create(struct ifstate *prev, struct place *p, bool startstate)
128 {
129 	struct ifstate *is;
130 
131 	is = domalloc(sizeof(*is));
132 	is->prev = prev;
133 	if (p != NULL) {
134 		is->startplace = *p;
135 	} else {
136 		place_setbuiltin(&is->startplace, 1);
137 	}
138 	is->curtrue = startstate;
139 	is->evertrue = is->curtrue;
140 	is->seenelse = false;
141 	return is;
142 }
143 
144 static
145 void
146 ifstate_destroy(struct ifstate *is)
147 {
148 	dofree(is, sizeof(*is));
149 }
150 
151 static
152 void
153 ifstate_push(struct place *p, bool startstate)
154 {
155 	struct ifstate *newstate;
156 
157 	newstate = ifstate_create(ifstate, p, startstate);
158 	if (!ifstate->curtrue) {
159 		newstate->curtrue = false;
160 		newstate->evertrue = true;
161 	}
162 	ifstate = newstate;
163 }
164 
165 static
166 void
167 ifstate_pop(void)
168 {
169 	struct ifstate *is;
170 
171 	is = ifstate;
172 	ifstate = ifstate->prev;
173 	ifstate_destroy(is);
174 }
175 
176 static
177 void
178 d_if(struct place *p, struct place *p2, char *line)
179 {
180 	char *expr;
181 	bool val;
182 	struct place p3 = *p2;
183 	size_t oldlen;
184 
185 	expr = macroexpand(p2, line, strlen(line), true);
186 
187 	oldlen = strlen(expr);
188 	uncomment(expr);
189 	/* trim to fit, so the malloc debugging won't complain */
190 	expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1);
191 
192 	if (ifstate->curtrue) {
193 		val = eval(&p3, expr);
194 	} else {
195 		val = 0;
196 	}
197 	ifstate_push(p, val);
198 	dostrfree(expr);
199 }
200 
201 static
202 void
203 d_ifdef(struct place *p, struct place *p2, char *line)
204 {
205 	uncomment(line);
206 	oneword("#ifdef", p2, line);
207 	ifstate_push(p, macro_isdefined(line));
208 }
209 
210 static
211 void
212 d_ifndef(struct place *p, struct place *p2, char *line)
213 {
214 	uncomment(line);
215 	oneword("#ifndef", p2, line);
216 	ifstate_push(p, !macro_isdefined(line));
217 }
218 
219 static
220 void
221 d_elif(struct place *p, struct place *p2, char *line)
222 {
223 	char *expr;
224 	struct place p3 = *p2;
225 	size_t oldlen;
226 
227 	if (ifstate->seenelse) {
228 		complain(p, "#elif after #else");
229 		complain_fail();
230 	}
231 
232 	if (ifstate->evertrue) {
233 		ifstate->curtrue = false;
234 	} else {
235 		expr = macroexpand(p2, line, strlen(line), true);
236 
237 		oldlen = strlen(expr);
238 		uncomment(expr);
239 		/* trim to fit, so the malloc debugging won't complain */
240 		expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1);
241 
242 		ifstate->curtrue = eval(&p3, expr);
243 		ifstate->evertrue = ifstate->curtrue;
244 		dostrfree(expr);
245 	}
246 }
247 
248 static
249 void
250 d_else(struct place *p, struct place *p2, char *line)
251 {
252 	(void)p2;
253 	(void)line;
254 
255 	if (ifstate->seenelse) {
256 		complain(p, "Multiple #else directives in one conditional");
257 		complain_fail();
258 	}
259 
260 	ifstate->curtrue = !ifstate->evertrue;
261 	ifstate->evertrue = true;
262 	ifstate->seenelse = true;
263 }
264 
265 static
266 void
267 d_endif(struct place *p, struct place *p2, char *line)
268 {
269 	(void)p2;
270 	(void)line;
271 
272 	if (ifstate->prev == NULL) {
273 		complain(p, "Unmatched #endif");
274 		complain_fail();
275 	} else {
276 		ifstate_pop();
277 	}
278 }
279 
280 ////////////////////////////////////////////////////////////
281 // macros
282 
283 static
284 void
285 d_define(struct place *p, struct place *p2, char *line)
286 {
287 	size_t pos, argpos;
288 	struct place p3, p4;
289 
290 	(void)p;
291 
292 	/*
293 	 * line may be:
294 	 *    macro expansion
295 	 *    macro(arg, arg, ...) expansion
296 	 */
297 
298 	pos = strcspn(line, " \t\f\v(");
299 	if (line[pos] == '(') {
300 		line[pos++] = '\0';
301 		argpos = pos;
302 		pos = pos + strcspn(line+pos, "()");
303 		if (line[pos] == '(') {
304 			p2->column += pos;
305 			complain(p2, "Left parenthesis in macro parameters");
306 			complain_fail();
307 			return;
308 		}
309 		if (line[pos] != ')') {
310 			p2->column += pos;
311 			complain(p2, "Unclosed macro parameter list");
312 			complain_fail();
313 			return;
314 		}
315 		line[pos++] = '\0';
316 #if 0
317 		if (!strchr(ws, line[pos])) {
318 			p2->column += pos;
319 			complain(p2, "Trash after macro parameter list");
320 			complain_fail();
321 			return;
322 		}
323 #endif
324 	} else if (line[pos] == '\0') {
325 		argpos = 0;
326 	} else {
327 		line[pos++] = '\0';
328 		argpos = 0;
329 	}
330 
331 	pos += strspn(line+pos, ws);
332 
333 	p3 = *p2;
334 	p3.column += argpos;
335 
336 	p4 = *p2;
337 	p4.column += pos;
338 
339 	if (argpos) {
340 		macro_define_params(p2, line, &p3,
341 				    line + argpos, &p4,
342 				    line + pos);
343 	} else {
344 		macro_define_plain(p2, line, &p4, line + pos);
345 	}
346 }
347 
348 static
349 void
350 d_undef(struct place *p, struct place *p2, char *line)
351 {
352 	(void)p;
353 
354 	uncomment(line);
355 	oneword("#undef", p2, line);
356 	macro_undef(line);
357 }
358 
359 ////////////////////////////////////////////////////////////
360 // includes
361 
362 static
363 bool
364 tryinclude(struct place *p, char *line)
365 {
366 	size_t len;
367 
368 	len = strlen(line);
369 	if (len > 2 && line[0] == '"' && line[len-1] == '"') {
370 		line[len-1] = '\0';
371 		file_readquote(p, line+1);
372 		line[len-1] = '"';
373 		return true;
374 	}
375 	if (len > 2 && line[0] == '<' && line[len-1] == '>') {
376 		line[len-1] = '\0';
377 		file_readbracket(p, line+1);
378 		line[len-1] = '>';
379 		return true;
380 	}
381 	return false;
382 }
383 
384 static
385 void
386 d_include(struct place *p, struct place *p2, char *line)
387 {
388 	char *text;
389 	size_t oldlen;
390 
391 	uncomment(line);
392 	if (tryinclude(p, line)) {
393 		return;
394 	}
395 	text = macroexpand(p2, line, strlen(line), false);
396 
397 	oldlen = strlen(text);
398 	uncomment(text);
399 	/* trim to fit, so the malloc debugging won't complain */
400 	text = dorealloc(text, oldlen + 1, strlen(text) + 1);
401 
402 	if (tryinclude(p, text)) {
403 		dostrfree(text);
404 		return;
405 	}
406 	complain(p, "Illegal #include directive");
407 	complain(p, "Before macro expansion: #include %s", line);
408 	complain(p, "After macro expansion: #include %s", text);
409 	dostrfree(text);
410 	complain_fail();
411 }
412 
413 static
414 void
415 d_line(struct place *p, struct place *p2, char *line)
416 {
417 	(void)p2;
418 	(void)line;
419 
420 	/* XXX */
421 	complain(p, "Sorry, no #line yet");
422 }
423 
424 ////////////////////////////////////////////////////////////
425 // messages
426 
427 static
428 void
429 d_warning(struct place *p, struct place *p2, char *line)
430 {
431 	char *msg;
432 
433 	msg = macroexpand(p2, line, strlen(line), false);
434 	complain(p, "#warning: %s", msg);
435 	if (mode.werror) {
436 		complain_fail();
437 	}
438 	dostrfree(msg);
439 }
440 
441 static
442 void
443 d_error(struct place *p, struct place *p2, char *line)
444 {
445 	char *msg;
446 
447 	msg = macroexpand(p2, line, strlen(line), false);
448 	complain(p, "#error: %s", msg);
449 	complain_fail();
450 	dostrfree(msg);
451 }
452 
453 ////////////////////////////////////////////////////////////
454 // other
455 
456 static
457 void
458 d_pragma(struct place *p, struct place *p2, char *line)
459 {
460 	(void)p2;
461 
462 	complain(p, "#pragma %s", line);
463 	complain_fail();
464 }
465 
466 ////////////////////////////////////////////////////////////
467 // directive table
468 
469 static const struct {
470 	const char *name;
471 	bool ifskip;
472 	void (*func)(struct place *, struct place *, char *line);
473 } directives[] = {
474 	{ "define",  true,  d_define },
475 	{ "elif",    false, d_elif },
476 	{ "else",    false, d_else },
477 	{ "endif",   false, d_endif },
478 	{ "error",   true,  d_error },
479 	{ "if",      false, d_if },
480 	{ "ifdef",   false, d_ifdef },
481 	{ "ifndef",  false, d_ifndef },
482 	{ "include", true,  d_include },
483 	{ "line",    true,  d_line },
484 	{ "pragma",  true,  d_pragma },
485 	{ "undef",   true,  d_undef },
486 	{ "warning", true,  d_warning },
487 };
488 static const unsigned numdirectives = HOWMANY(directives);
489 
490 static
491 void
492 directive_gotdirective(struct place *p, char *line)
493 {
494 	struct place p2;
495 	size_t len, skip;
496 	unsigned i;
497 
498 	p2 = *p;
499 	for (i=0; i<numdirectives; i++) {
500 		len = strlen(directives[i].name);
501 		if (!strncmp(line, directives[i].name, len) &&
502 		    strchr(ws, line[len])) {
503 			if (directives[i].ifskip && !ifstate->curtrue) {
504 				return;
505 			}
506 			skip = len + strspn(line+len, ws);
507 			p2.column += skip;
508 			line += skip;
509 
510 			len = strlen(line);
511 			len = notrailingws(line, len);
512 			if (len < strlen(line)) {
513 				line[len] = '\0';
514 			}
515 			directives[i].func(p, &p2, line);
516 			return;
517 		}
518 	}
519 	/* ugh. allow # by itself, including with a comment after it */
520 	uncomment(line);
521 	if (line[0] == '\0') {
522 		return;
523 	}
524 
525 	skip = strcspn(line, ws);
526 	complain(p, "Unknown directive #%.*s", (int)skip, line);
527 	complain_fail();
528 }
529 
530 /*
531  * Check for nested comment delimiters in LINE.
532  */
533 static
534 size_t
535 directive_scancomments(const struct place *p, char *line, size_t len)
536 {
537 	size_t pos;
538 	bool incomment;
539 	struct place p2;
540 
541 	p2 = *p;
542 	incomment = 0;
543 	for (pos = 0; pos+1 < len; pos++) {
544 		if (line[pos] == '/' && line[pos+1] == '*') {
545 			if (incomment) {
546 				complain(&p2, "Warning: %c%c within comment",
547 					 '/', '*');
548 				if (mode.werror) {
549 					complain_failed();
550 				}
551 			} else {
552 				incomment = true;
553 			}
554 			pos++;
555 		} else if (line[pos] == '*' && line[pos+1] == '/') {
556 			if (incomment) {
557 				incomment = false;
558 			} else {
559 				/* stray end-comment; should we care? */
560 			}
561 			pos++;
562 		}
563 		if (line[pos] == '\n') {
564 			p2.line++;
565 			p2.column = 0;
566 		} else {
567 			p2.column++;
568 		}
569 	}
570 
571 	/* multiline comments are supposed to arrive in a single buffer */
572 	assert(!incomment);
573 	return len;
574 }
575 
576 void
577 directive_gotline(struct place *p, char *line, size_t len)
578 {
579 	size_t skip;
580 
581 	if (warns.nestcomment) {
582 		directive_scancomments(p, line, len);
583 	}
584 
585 	/* check if we have a directive line (# exactly in column 0) */
586 	if (line[0] == '#') {
587 		skip = 1 + strspn(line + 1, ws);
588 		assert(skip <= len);
589 		p->column += skip;
590 		assert(line[len] == '\0');
591 		directive_gotdirective(p, line+skip /*, length = len-skip */);
592 		p->column += len-skip;
593 	} else if (ifstate->curtrue) {
594 		macro_sendline(p, line, len);
595 		p->column += len;
596 	}
597 }
598 
599 void
600 directive_goteof(struct place *p)
601 {
602 	while (ifstate->prev != NULL) {
603 		complain(p, "Missing #endif");
604 		complain(&ifstate->startplace, "...opened at this point");
605 		complain_failed();
606 		ifstate_pop();
607 	}
608 	macro_sendeof(p);
609 }
610 
611 ////////////////////////////////////////////////////////////
612 // module initialization
613 
614 void
615 directive_init(void)
616 {
617 	ifstate = ifstate_create(NULL, NULL, true);
618 }
619 
620 void
621 directive_cleanup(void)
622 {
623 	assert(ifstate->prev == NULL);
624 	ifstate_destroy(ifstate);
625 	ifstate = NULL;
626 }
627