Edit

IABSD.fr/src/libexec/tradcpp/directive.c

Branch :

  • Show log

    Commit

  • Author : jsg
    Date : 2019-08-23 04:38:55
    Hash : 88157d21
    Message : update tradcpp to 0.5.3

  • libexec/tradcpp/directive.c
  • /*-
     * Copyright (c) 2010, 2013 The NetBSD Foundation, Inc.
     * All rights reserved.
     *
     * This code is derived from software contributed to The NetBSD Foundation
     * by David A. Holland.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 1. Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer.
     * 2. Redistributions in binary form must reproduce the above copyright
     *    notice, this list of conditions and the following disclaimer in the
     *    documentation and/or other materials provided with the distribution.
     *
     * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     * POSSIBILITY OF SUCH DAMAGE.
     */
    
    #include <assert.h>
    #include <stdlib.h>
    #include <string.h>
    #include <limits.h>
    #include <errno.h>
    
    #include "bool.h"
    #include "utils.h"
    #include "mode.h"
    #include "place.h"
    #include "files.h"
    #include "directive.h"
    #include "macro.h"
    #include "eval.h"
    #include "output.h"
    
    struct ifstate {
    	struct ifstate *prev;
    	struct place startplace;
    	bool curtrue;
    	bool evertrue;
    	bool seenelse;
    };
    
    static struct ifstate *ifstate;
    
    ////////////////////////////////////////////////////////////
    // common parsing bits
    
    static
    void
    uncomment(char *buf)
    {
    	char *s, *t, *u = NULL;
    	bool incomment = false;
    	bool inesc = false;
    	bool inquote = false;
    	char quote = '\0';
    
    	for (s = t = buf; *s; s++) {
    		if (incomment) {
    			if (s[0] == '*' && s[1] == '/') {
    				s++;
    				incomment = false;
    			}
    		} else {
    			if (!inquote && s[0] == '/' && s[1] == '*') {
    				incomment = true;
    			} else {
    				if (inesc) {
    					inesc = false;
    				} else if (s[0] == '\\') {
    					inesc = true;
    				} else if (!inquote &&
    					   (s[0] == '"' || s[0] == '\'')) {
    					inquote = true;
    					quote = s[0];
    				} else if (inquote && s[0] == quote) {
    					inquote = false;
    				}
    
    				if (t != s) {
    					*t = *s;
    				}
    				if (!strchr(ws, *t)) {
    					u = t;
    				}
    				t++;
    			}
    		}
    	}
    	if (u) {
    		/* end string after last non-whitespace char */
    		u[1] = '\0';
    	} else {
    		*t = '\0';
    	}
    }
    
    static
    void
    oneword(const char *what, struct place *p2, char *line)
    {
    	size_t pos;
    
    	pos = strcspn(line, ws);
    	if (line[pos] != '\0') {
    		place_addcolumns(p2, pos);
    		complain(p2, "Garbage after %s argument", what);
    		complain_fail();
    		line[pos] = '\0';
    	}
    }
    
    ////////////////////////////////////////////////////////////
    // if handling
    
    static
    struct ifstate *
    ifstate_create(struct ifstate *prev, struct place *p, bool startstate)
    {
    	struct ifstate *is;
    
    	is = domalloc(sizeof(*is));
    	is->prev = prev;
    	if (p != NULL) {
    		is->startplace = *p;
    	} else {
    		place_setbuiltin(&is->startplace, 1);
    	}
    	is->curtrue = startstate;
    	is->evertrue = is->curtrue;
    	is->seenelse = false;
    	return is;
    }
    
    static
    void
    ifstate_destroy(struct ifstate *is)
    {
    	dofree(is, sizeof(*is));
    }
    
    static
    void
    ifstate_push(struct place *p, bool startstate)
    {
    	struct ifstate *newstate;
    
    	newstate = ifstate_create(ifstate, p, startstate);
    	if (!ifstate->curtrue) {
    		newstate->curtrue = false;
    		newstate->evertrue = true;
    	}
    	ifstate = newstate;
    }
    
    static
    void
    ifstate_pop(void)
    {
    	struct ifstate *is;
    
    	is = ifstate;
    	ifstate = ifstate->prev;
    	ifstate_destroy(is);
    }
    
    static
    void
    d_if(struct lineplace *lp, struct place *p2, char *line)
    {
    	bool doprint;
    	char *expr;
    	bool val;
    	struct place p3 = *p2;
    	size_t oldlen;
    
    	doprint = ifstate->curtrue;
    
    	expr = macroexpand(p2, line, strlen(line), true);
    
    	oldlen = strlen(expr);
    	uncomment(expr);
    	/* trim to fit, so the malloc debugging won't complain */
    	expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1);
    
    	if (ifstate->curtrue) {
    		val = eval(&p3, expr);
    	} else {
    		val = 0;
    	}
    	ifstate_push(&lp->current, val);
    	dostrfree(expr);
    
    	if (doprint) {
    		debuglog(&lp->current, "#if: %s",
    			  ifstate->curtrue ? "taken" : "not taken");
    	}
    }
    
    static
    void
    d_ifdef(struct lineplace *lp, struct place *p2, char *line)
    {
    	bool doprint;
    
    	doprint = ifstate->curtrue;
    
    	uncomment(line);
    	oneword("#ifdef", p2, line);
    	ifstate_push(&lp->current, macro_isdefined(line));
    
    	if (doprint) {
    		debuglog(&lp->current, "#ifdef %s: %s",
    			 line, ifstate->curtrue ? "taken" : "not taken");
    	}
    }
    
    static
    void
    d_ifndef(struct lineplace *lp, struct place *p2, char *line)
    {
    	bool doprint;
    
    	doprint = ifstate->curtrue;
    
    	uncomment(line);
    	oneword("#ifndef", p2, line);
    	ifstate_push(&lp->current, !macro_isdefined(line));
    
    	if (doprint) {
    		debuglog(&lp->current, "#ifndef %s: %s",
    			 line, ifstate->curtrue ? "taken" : "not taken");
    	}
    }
    
    static
    void
    d_elif(struct lineplace *lp, struct place *p2, char *line)
    {
    	bool doprint;
    	char *expr;
    	struct place p3 = *p2;
    	size_t oldlen;
    
    	if (ifstate->seenelse) {
    		complain(&lp->current, "#elif after #else");
    		complain_fail();
    	}
    
    	doprint = ifstate->curtrue;
    
    	if (ifstate->evertrue) {
    		ifstate->curtrue = false;
    	} else {
    		expr = macroexpand(p2, line, strlen(line), true);
    
    		oldlen = strlen(expr);
    		uncomment(expr);
    		/* trim to fit, so the malloc debugging won't complain */
    		expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1);
    
    		ifstate->curtrue = eval(&p3, expr);
    		ifstate->evertrue = ifstate->curtrue;
    		dostrfree(expr);
    	}
    
    	if (doprint) {
    		debuglog2(&lp->current, &ifstate->startplace, "#elif: %s",
    			  ifstate->curtrue ? "taken" : "not taken");
    	}
    }
    
    static
    void
    d_else(struct lineplace *lp, struct place *p2, char *line)
    {
    	bool doprint;
    
    	(void)p2;
    	(void)line;
    
    	if (ifstate->seenelse) {
    		complain(&lp->current,
    			 "Multiple #else directives in one conditional");
    		complain_fail();
    	}
    
    	doprint = ifstate->curtrue;
    
    	ifstate->curtrue = !ifstate->evertrue;
    	ifstate->evertrue = true;
    	ifstate->seenelse = true;
    
    	if (doprint) {
    		debuglog2(&lp->current, &ifstate->startplace, "#else: %s",
    			  ifstate->curtrue ? "taken" : "not taken");
    	}
    }
    
    static
    void
    d_endif(struct lineplace *lp, struct place *p2, char *line)
    {
    	(void)p2;
    	(void)line;
    
    	if (ifstate->prev == NULL) {
    		complain(&lp->current, "Unmatched #endif");
    		complain_fail();
    	} else {
    		debuglog2(&lp->current, &ifstate->startplace, "#endif");
    		ifstate_pop();
    	}
    }
    
    ////////////////////////////////////////////////////////////
    // macros
    
    static
    void
    d_define(struct lineplace *lp, struct place *p2, char *line)
    {
    	size_t pos, argpos;
    	struct place p3, p4;
    
    	(void)lp;
    
    	/*
    	 * line may be:
    	 *    macro expansion
    	 *    macro(arg, arg, ...) expansion
    	 */
    
    	pos = strcspn(line, " \t\f\v(");
    	if (line[pos] == '(') {
    		line[pos++] = '\0';
    		argpos = pos;
    		pos = pos + strcspn(line+pos, "()");
    		if (line[pos] == '(') {
    			place_addcolumns(p2, pos);
    			complain(p2, "Left parenthesis in macro parameters");
    			complain_fail();
    			return;
    		}
    		if (line[pos] != ')') {
    			place_addcolumns(p2, pos);
    			complain(p2, "Unclosed macro parameter list");
    			complain_fail();
    			return;
    		}
    		line[pos++] = '\0';
    #if 0
    		if (!strchr(ws, line[pos])) {
    			p2->column += pos;
    			complain(p2, "Trash after macro parameter list");
    			complain_fail();
    			return;
    		}
    #endif
    	} else if (line[pos] == '\0') {
    		argpos = 0;
    	} else {
    		line[pos++] = '\0';
    		argpos = 0;
    	}
    
    	pos += strspn(line+pos, ws);
    
    	p3 = *p2;
    	place_addcolumns(&p3, argpos);
    
    	p4 = *p2;
    	place_addcolumns(&p4, pos);
    
    	if (argpos) {
    		debuglog(&lp->current, "Defining %s()", line);
    		macro_define_params(p2, line, &p3,
    				    line + argpos, &p4,
    				    line + pos);
    	} else {
    		debuglog(&lp->current, "Defining %s", line);
    		macro_define_plain(p2, line, &p4, line + pos);
    	}
    }
    
    static
    void
    d_undef(struct lineplace *lp, struct place *p2, char *line)
    {
    	(void)lp;
    
    	uncomment(line);
    	oneword("#undef", p2, line);
    	debuglog(&lp->current, "Undef %s", line);
    	macro_undef(line);
    }
    
    ////////////////////////////////////////////////////////////
    // includes
    
    static
    bool
    tryinclude(struct place *p, char *line)
    {
    	size_t len;
    
    	len = strlen(line);
    	if (len > 2 && line[0] == '"' && line[len-1] == '"') {
    		line[len-1] = '\0';
    		debuglog(p, "Entering include file \"%s\"", line+1);
    		file_readquote(p, line+1);
    		debuglog(p, "Leaving include file \"%s\"", line+1);
    		line[len-1] = '"';
    		return true;
    	}
    	if (len > 2 && line[0] == '<' && line[len-1] == '>') {
    		line[len-1] = '\0';
    		debuglog(p, "Entering include file <%s>", line+1);
    		file_readbracket(p, line+1);
    		debuglog(p, "Leaving include file <%s>", line+1);
    		line[len-1] = '>';
    		return true;
    	}
    	return false;
    }
    
    static
    void
    d_include(struct lineplace *lp, struct place *p2, char *line)
    {
    	char *text;
    	size_t oldlen;
    
    	uncomment(line);
    	if (tryinclude(&lp->current, line)) {
    		return;
    	}
    	text = macroexpand(p2, line, strlen(line), false);
    
    	oldlen = strlen(text);
    	uncomment(text);
    	/* trim to fit, so the malloc debugging won't complain */
    	text = dorealloc(text, oldlen + 1, strlen(text) + 1);
    
    	if (tryinclude(&lp->current, text)) {
    		dostrfree(text);
    		return;
    	}
    	complain(&lp->current, "Illegal #include directive");
    	complain(&lp->current, "Before macro expansion: #include %s", line);
    	complain(&lp->current, "After macro expansion: #include %s", text);
    	dostrfree(text);
    	complain_fail();
    }
    
    static
    void
    d_line(struct lineplace *lp, struct place *p2, char *line)
    {
    	char *text;
    	size_t oldlen;
    	unsigned long val;
    	char *moretext;
    	size_t moretextlen;
    	char *filename;
    
    	text = macroexpand(p2, line, strlen(line), true);
    
    	oldlen = strlen(text);
    	uncomment(text);
    	/* trim to fit, so the malloc debugging won't complain */
    	text = dorealloc(text, oldlen + 1, strlen(text) + 1);
    
    	/*
    	 * What we should have here: either 1234 "file.c",
    	 * or just 1234.
    	 */
    
    	errno = 0;
    	val = strtoul(text, &moretext, 10);
    	if (errno) {
    		complain(&lp->current,
    			 "Invalid line number in #line directive");
    		goto fail;
    	}
    #if UINT_MAX < ULONG_MAX
    	if (val > UINT_MAX) {
    		complain(&lp->current,
    			 "Line number in #line directive too large");
    		goto fail;
    	}
    #endif
    	moretext += strspn(moretext, ws);
    	moretextlen = strlen(moretext);
    	place_addcolumns(&lp->current, moretext - text);
    
    	if (moretextlen > 2 &&
    	    moretext[0] == '"' && moretext[moretextlen-1] == '"') {
    		filename = dostrndup(moretext+1, moretextlen-2);
    		place_changefile(&lp->nextline, filename);
    		dostrfree(filename);
    	}
    	else if (moretextlen > 0) {
    		complain(&lp->current,
    			 "Invalid file name in #line directive");
    		goto fail;
    	}
    
    	lp->nextline.line = val;
    	dostrfree(text);
    	return;
    
    fail:
    	complain(&lp->current, "Before macro expansion: #line %s", line);
    	complain(&lp->current, "After macro expansion: #line %s", text);
    	complain_fail();
    	dostrfree(text);
    }
    
    ////////////////////////////////////////////////////////////
    // messages
    
    static
    void
    d_warning(struct lineplace *lp, struct place *p2, char *line)
    {
    	char *msg;
    
    	msg = macroexpand(p2, line, strlen(line), false);
    	complain(&lp->current, "#warning: %s", msg);
    	if (mode.werror) {
    		complain_fail();
    	}
    	dostrfree(msg);
    }
    
    static
    void
    d_error(struct lineplace *lp, struct place *p2, char *line)
    {
    	char *msg;
    
    	msg = macroexpand(p2, line, strlen(line), false);
    	complain(&lp->current, "#error: %s", msg);
    	complain_fail();
    	dostrfree(msg);
    }
    
    ////////////////////////////////////////////////////////////
    // other
    
    static
    void
    d_pragma(struct lineplace *lp, struct place *p2, char *line)
    {
    	(void)p2;
    
    	complain(&lp->current, "#pragma %s", line);
    	complain_fail();
    }
    
    ////////////////////////////////////////////////////////////
    // directive table
    
    static const struct {
    	const char *name;
    	bool ifskip;
    	void (*func)(struct lineplace *, struct place *, char *line);
    } directives[] = {
    	{ "define",  true,  d_define },
    	{ "elif",    false, d_elif },
    	{ "else",    false, d_else },
    	{ "endif",   false, d_endif },
    	{ "error",   true,  d_error },
    	{ "if",      false, d_if },
    	{ "ifdef",   false, d_ifdef },
    	{ "ifndef",  false, d_ifndef },
    	{ "include", true,  d_include },
    	{ "line",    true,  d_line },
    	{ "pragma",  true,  d_pragma },
    	{ "undef",   true,  d_undef },
    	{ "warning", true,  d_warning },
    };
    static const unsigned numdirectives = HOWMANY(directives);
    
    static
    void
    directive_gotdirective(struct lineplace *lp, char *line)
    {
    	struct place p2;
    	size_t len, skip;
    	unsigned i;
    
    	p2 = lp->current;
    	for (i=0; i<numdirectives; i++) {
    		len = strlen(directives[i].name);
    		if (!strncmp(line, directives[i].name, len) &&
    		    strchr(ws, line[len])) {
    			if (directives[i].ifskip && !ifstate->curtrue) {
    				return;
    			}
    			skip = len + strspn(line+len, ws);
    			place_addcolumns(&p2, skip);
    			line += skip;
    
    			len = strlen(line);
    			len = notrailingws(line, len);
    			if (len < strlen(line)) {
    				line[len] = '\0';
    			}
    			directives[i].func(lp, &p2, line);
    			return;
    		}
    	}
    	/* ugh. allow # by itself, including with a comment after it */
    	uncomment(line);
    	if (line[0] == '\0') {
    		return;
    	}
    
    	skip = strcspn(line, ws);
    	complain(&lp->current, "Unknown directive #%.*s", (int)skip, line);
    	complain_fail();
    }
    
    /*
     * Check for nested comment delimiters in LINE.
     */
    static
    size_t
    directive_scancomments(const struct lineplace *lp, char *line, size_t len)
    {
    	size_t pos;
    	bool incomment;
    	struct place p2;
    
    	p2 = lp->current;
    	incomment = 0;
    	for (pos = 0; pos+1 < len; pos++) {
    		if (line[pos] == '/' && line[pos+1] == '*') {
    			if (incomment) {
    				complain(&p2, "Warning: %c%c within comment",
    					 '/', '*');
    				if (mode.werror) {
    					complain_failed();
    				}
    			} else {
    				incomment = true;
    			}
    			pos++;
    		} else if (line[pos] == '*' && line[pos+1] == '/') {
    			if (incomment) {
    				incomment = false;
    			} else {
    				/* stray end-comment; should we care? */
    			}
    			pos++;
    		}
    		if (line[pos] == '\n') {
    			place_addlines(&p2, 1);
    			p2.column = 0;
    		} else {
    			place_addcolumns(&p2, 1);
    		}
    	}
    
    	/* multiline comments are supposed to arrive in a single buffer */
    	assert(!incomment);
    	return len;
    }
    
    void
    directive_gotline(struct lineplace *lp, char *line, size_t len)
    {
    	size_t skip;
    
    	if (warns.nestcomment) {
    		directive_scancomments(lp, line, len);
    	}
    
    	/* check if we have a directive line (# exactly in column 0) */
    	if (len > 0 && line[0] == '#') {
    		skip = 1 + strspn(line + 1, ws);
    		assert(skip <= len);
    		place_addcolumns(&lp->current, skip);
    		assert(line[len] == '\0');
    		directive_gotdirective(lp, line+skip /*, length = len-skip */);
    		place_addcolumns(&lp->current, len-skip);
    	} else if (ifstate->curtrue) {
    		macro_sendline(&lp->current, line, len);
    		place_addcolumns(&lp->current, len);
    	}
    }
    
    void
    directive_goteof(struct place *p)
    {
    	while (ifstate->prev != NULL) {
    		complain(p, "Missing #endif");
    		complain(&ifstate->startplace, "...opened at this point");
    		complain_failed();
    		ifstate_pop();
    	}
    	macro_sendeof(p);
    }
    
    ////////////////////////////////////////////////////////////
    // module initialization
    
    void
    directive_init(void)
    {
    	ifstate = ifstate_create(NULL, NULL, true);
    }
    
    void
    directive_cleanup(void)
    {
    	assert(ifstate->prev == NULL);
    	ifstate_destroy(ifstate);
    	ifstate = NULL;
    }