view main.c @ 122:64c4de3709de

Complain if -[DIU] is given an empty argument. (Like many compilers over the years, we don't accept the argument appearing in the next argv word.) Warn about this in the man page too.
author David A. Holland
date Tue, 11 Jun 2013 18:32:41 -0400
parents 2b0b61fd1a36
children eaae8014a94a
line wrap: on
line source

/*-
 * Copyright (c) 2010 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 <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <err.h>

#include "version.h"
#include "config.h"
#include "utils.h"
#include "array.h"
#include "mode.h"
#include "place.h"
#include "files.h"
#include "directive.h"
#include "macro.h"

struct mode mode = {
	.werror = false,

	.input_allow_dollars = false,
	.input_tabstop = 8,

	.do_stdinc = true,
	.do_stddef = true,

	.do_output = true,
	.output_linenumbers = true,
	.output_retain_comments = false,
	.output_file = NULL,

	.do_depend = false,
	.depend_report_system = false,
	.depend_assume_generated = false,
	.depend_issue_fakerules = false,
	.depend_quote_target = true,
	.depend_target = NULL,
	.depend_file = NULL,

	.do_macrolist = false,
	.macrolist_include_stddef = false,
	.macrolist_include_expansions = false,

	.do_trace = false,
	.trace_namesonly = false,
	.trace_indented = false,
};

struct warns warns = {
	.endiflabels = true,
	.nestcomment = false,
	.undef = false,
	.unused = false,
};

/* this is always true, but can be set explicitly with -traditional */
static bool traditional = true;

////////////////////////////////////////////////////////////
// commandline macros

struct commandline_macro {
	struct place where;
	struct place where2;
	const char *macro;
	const char *expansion;
};

static struct array commandline_macros;

static
void
commandline_macros_init(void)
{
	array_init(&commandline_macros);
}

static
void
commandline_macros_cleanup(void)
{
	array_cleanup(&commandline_macros);
}

static
void
commandline_macro_add(const struct place *p, const char *macro,
		      const struct place *p2, const char *expansion)
{
	struct commandline_macro *cm;

	cm = domalloc(sizeof(*cm));
	cm->where = *p;
	cm->where2 = *p2;
	cm->macro = macro;
	cm->expansion = expansion;

	array_add(&commandline_macros, cm, NULL);
}

static
void
commandline_def(const struct place *p, char *str)
{
	struct place p2;
	char *val;

	if (*str == '\0') {
		warnx("-D: macro name expected");
		die();
	}

	val = strchr(str, '=');
	if (val != NULL) {
		*val = '\0';
		val++;
	}

	if (val) {
		p2 = *p;
		p2.column += strlen(str);
	} else {
		place_setbuiltin(&p2, 1);
	}
	commandline_macro_add(p, str, &p2, val ? val : "1");
}

static
void
commandline_undef(const struct place *p, char *str)
{
	if (*str == '\0') {
		warnx("-D: macro name expected");
		die();
	}
	commandline_macro_add(p, str, p, NULL);
}

static
void
apply_commandline_macros(void)
{
	struct commandline_macro *cm;
	unsigned i, num;

	num = array_num(&commandline_macros);
	for (i=0; i<num; i++) {
		cm = array_get(&commandline_macros, i);
		if (cm->expansion != NULL) {
			macro_define_plain(&cm->where, cm->macro,
					   &cm->where2, cm->expansion);
		} else {
			macro_undef(cm->macro);
		}
		dofree(cm, sizeof(*cm));
	}
	array_setsize(&commandline_macros, 0);
}

static
void
apply_builtin_macro(unsigned num, const char *name, const char *val)
{
	struct place p;

	place_setbuiltin(&p, num);
	macro_define_plain(&p, name, &p, val);
}

static
void
apply_builtin_macros(void)
{
	unsigned n = 1;

#ifdef CONFIG_OS
	apply_builtin_macro(n++, CONFIG_OS, "1");
#endif
#ifdef CONFIG_OS_2
	apply_builtin_macro(n++, CONFIG_OS_2, "1");
#endif

#ifdef CONFIG_CPU
	apply_builtin_macro(n++, CONFIG_CPU, "1");
#endif
#ifdef CONFIG_CPU_2
	apply_builtin_macro(n++, CONFIG_CPU_2, "1");
#endif

#ifdef CONFIG_SIZE
	apply_builtin_macro(n++, CONFIG_SIZE, "1");
#endif
#ifdef CONFIG_BINFMT
	apply_builtin_macro(n++, CONFIG_BINFMT, "1");
#endif

#ifdef CONFIG_COMPILER
	apply_builtin_macro(n++, CONFIG_COMPILER, VERSION_MAJOR);
	apply_builtin_macro(n++, CONFIG_COMPILER_MINOR, VERSION_MINOR);
	apply_builtin_macro(n++, "__VERSION__", VERSION_LONG);
#endif
}

////////////////////////////////////////////////////////////
// extra included files

struct commandline_file {
	struct place where;
	char *name;
	bool suppress_output;
};

static struct array commandline_files;

static
void
commandline_files_init(void)
{
	array_init(&commandline_files);
}

static
void
commandline_files_cleanup(void)
{
	array_cleanup(&commandline_files);
}

static
void
commandline_addfile(const struct place *p, char *name, bool suppress_output)
{
	struct commandline_file *cf;

	cf = domalloc(sizeof(*cf));
	cf->where = *p;
	cf->name = name;
	cf->suppress_output = suppress_output;
	array_add(&commandline_files, cf, NULL);
}

static
void
commandline_addfile_output(const struct place *p, char *name)
{
	commandline_addfile(p, name, false);
}

static
void
commandline_addfile_nooutput(const struct place *p, char *name)
{
	commandline_addfile(p, name, true);
}

static
void
read_commandline_files(void)
{
	struct commandline_file *cf;
	unsigned i, num;
	bool save = false;

	num = array_num(&commandline_files);
	for (i=0; i<num; i++) {
		cf = array_get(&commandline_files, i);
		if (cf->suppress_output) {
			save = mode.do_output;
			mode.do_output = false;
			file_readquote(&cf->where, cf->name);
			mode.do_output = save;
		} else {
			file_readquote(&cf->where, cf->name);
		}
		dofree(cf, sizeof(*cf));
	}
	array_setsize(&commandline_files, 0);
}

////////////////////////////////////////////////////////////
// include path accumulation

static struct stringarray incpath_quote;
static struct stringarray incpath_user;
static struct stringarray incpath_system;
static struct stringarray incpath_late;
static const char *sysroot;

static
void
incpath_init(void)
{
	stringarray_init(&incpath_quote);
	stringarray_init(&incpath_user);
	stringarray_init(&incpath_system);
	stringarray_init(&incpath_late);
}

static
void
incpath_cleanup(void)
{
	stringarray_setsize(&incpath_quote, 0);
	stringarray_setsize(&incpath_user, 0);
	stringarray_setsize(&incpath_system, 0);
	stringarray_setsize(&incpath_late, 0);

	stringarray_cleanup(&incpath_quote);
	stringarray_cleanup(&incpath_user);
	stringarray_cleanup(&incpath_system);
	stringarray_cleanup(&incpath_late);
}

static
void
commandline_isysroot(const struct place *p, char *dir)
{
	(void)p;
	sysroot = dir;
}

static
void
commandline_addincpath(struct stringarray *arr, char *s)
{
	if (*s == '\0') {
		warnx("Empty include path");
		die();
	}
	stringarray_add(arr, s, NULL);
}

static
void
commandline_addincpath_quote(const struct place *p, char *dir)
{
	(void)p;
	commandline_addincpath(&incpath_quote, dir);
}

static
void
commandline_addincpath_user(const struct place *p, char *dir)
{
	(void)p;
	commandline_addincpath(&incpath_user, dir);
}

static
void
commandline_addincpath_system(const struct place *p, char *dir)
{
	(void)p;
	commandline_addincpath(&incpath_system, dir);
}

static
void
commandline_addincpath_late(const struct place *p, char *dir)
{
	(void)p;
	commandline_addincpath(&incpath_late, dir);
}

static
void
loadincludepath(void)
{
	unsigned i, num;
	const char *dir;
	char *t;

	num = stringarray_num(&incpath_quote);
	for (i=0; i<num; i++) {
		dir = stringarray_get(&incpath_quote, i);
		files_addquotepath(dir, false);
	}
	files_addquotepath(NULL, false);

	num = stringarray_num(&incpath_user);
	for (i=0; i<num; i++) {
		dir = stringarray_get(&incpath_user, i);
		files_addquotepath(dir, false);
		files_addbracketpath(dir, false);
	}

	if (mode.do_stdinc) {
		if (sysroot != NULL) {
			t = dostrdup3(sysroot, "/", CONFIG_LOCALINCLUDE);
			freestringlater(t);
			dir = t;
		} else {
			dir = CONFIG_LOCALINCLUDE;
		}
		files_addquotepath(dir, true);
		files_addbracketpath(dir, true);

		if (sysroot != NULL) {
			t = dostrdup3(sysroot, "/", CONFIG_SYSTEMINCLUDE);
			freestringlater(t);
			dir = t;
		} else {
			dir = CONFIG_SYSTEMINCLUDE;
		}
		files_addquotepath(dir, true);
		files_addbracketpath(dir, true);
	}

	num = stringarray_num(&incpath_system);
	for (i=0; i<num; i++) {
		dir = stringarray_get(&incpath_system, i);
		files_addquotepath(dir, true);
		files_addbracketpath(dir, true);
	}

	num = stringarray_num(&incpath_late);
	for (i=0; i<num; i++) {
		dir = stringarray_get(&incpath_late, i);
		files_addquotepath(dir, false);
		files_addbracketpath(dir, false);
	}
}

////////////////////////////////////////////////////////////
// silly commandline stuff

static const char *commandline_prefix;

static
void
commandline_setprefix(const struct place *p, char *prefix)
{
	(void)p;
	commandline_prefix = prefix;
}

static
void
commandline_addincpath_user_withprefix(const struct place *p, char *dir)
{
	char *s;

	if (commandline_prefix == NULL) {
		warnx("-iprefix needed");
		die();
	}
	s = dostrdup3(commandline_prefix, "/", dir);
	freestringlater(s);
	commandline_addincpath_user(p, s);
}

static
void
commandline_addincpath_late_withprefix(const struct place *p, char *dir)
{
	char *s;

	if (commandline_prefix == NULL) {
		warnx("-iprefix needed");
		die();
	}
	s = dostrdup3(commandline_prefix, "/", dir);
	freestringlater(s);
	commandline_addincpath_late(p, s);
}

static
void
commandline_setstd(const struct place *p, char *std)
{
	(void)p;

	if (!strcmp(std, "krc")) {
		return;
	}
	warnx("Standard %s not supported by this preprocessor", std);
	die();
}

static
void
commandline_setlang(const struct place *p, char *lang)
{
	(void)p;

	if (!strcmp(lang, "c") || !strcmp(lang, "assembler-with-cpp")) {
		return;
	}
	warnx("Language %s not supported by this preprocessor", lang);
	die();
}

////////////////////////////////////////////////////////////
// complex modes

DEAD static
void
commandline_iremap(const struct place *p, char *str)
{
	(void)p;
	/* XXX */
	(void)str;
	warnx("-iremap not supported");
	die();
}

static
void
commandline_tabstop(const struct place *p, char *s)
{
	char *t;
	unsigned long val;

	(void)p;

	t = strchr(s, '=');
	if (t == NULL) {
		/* should not happen */
		warnx("Invalid tabstop");
		die();
	}
	t++;
	errno = 0;
	val = strtoul(t, &t, 10);
	if (errno || *t != '\0') {
		warnx("Invalid tabstop");
		die();
	}
	if (val > 64) {
		warnx("Preposterously large tabstop");
		die();
	}
	mode.input_tabstop = val;
}

/*
 * macrolist
 */

static
void
commandline_dD(void)
{
	mode.do_macrolist = true;
	mode.macrolist_include_stddef = false;
	mode.macrolist_include_expansions = true;
}

static
void
commandline_dM(void)
{
	mode.do_macrolist = true;
	mode.macrolist_include_stddef = true;
	mode.macrolist_include_expansions = true;
	mode.do_output = false;
}

static
void
commandline_dN(void)
{
	mode.do_macrolist = true;
	mode.macrolist_include_stddef = false;
	mode.macrolist_include_expansions = false;
}

/*
 * include trace
 */

static
void
commandline_dI(void)
{
	mode.do_trace = true;
	mode.trace_namesonly = false;
	mode.trace_indented = false;
}

static
void
commandline_H(void)
{
	mode.do_trace = true;
	mode.trace_namesonly = true;
	mode.trace_indented = true;
}

/*
 * depend
 */

static
void
commandline_setdependtarget(const struct place *p, char *str)
{
	(void)p;
	mode.depend_target = str;
	mode.depend_quote_target = false;
}

static
void
commandline_setdependtarget_quoted(const struct place *p, char *str)
{
	(void)p;
	mode.depend_target = str;
	mode.depend_quote_target = true;
}

static
void
commandline_setdependoutput(const struct place *p, char *str)
{
	(void)p;
	mode.depend_file = str;
}

static
void
commandline_M(void)
{
	mode.do_depend = true;
	mode.depend_report_system = true;
	mode.do_output = false;
}

static
void
commandline_MM(void)
{
	mode.do_depend = true;
	mode.depend_report_system = false;
	mode.do_output = false;
}

static
void
commandline_MD(void)
{
	mode.do_depend = true;
	mode.depend_report_system = true;
}

static
void
commandline_MMD(void)
{
	mode.do_depend = true;
	mode.depend_report_system = false;
}

static
void
commandline_wall(void)
{
	warns.nestcomment = true;
	warns.undef = true;
	warns.unused = true;
}

static
void
commandline_wnoall(void)
{
	warns.nestcomment = false;
	warns.undef = false;
	warns.unused = false;
}

static
void
commandline_wnone(void)
{
	warns.nestcomment = false;
	warns.endiflabels = false;
	warns.undef = false;
	warns.unused = false;
}

////////////////////////////////////////////////////////////
// options

struct flag_option {
	const char *string;
	bool *flag;
	bool setto;
};

struct act_option {
	const char *string;
	void (*func)(void);
};

struct prefix_option {
	const char *string;
	void (*func)(const struct place *, char *);
};

struct arg_option {
	const char *string;
	void (*func)(const struct place *, char *);
};

static const struct flag_option flag_options[] = {
	{ "C",                          &mode.output_retain_comments,  true },
	{ "CC",                         &mode.output_retain_comments,  true },
	{ "MG",                         &mode.depend_assume_generated, true },
	{ "MP",                         &mode.depend_issue_fakerules,  true },
	{ "P",                          &mode.output_linenumbers,      false },
	{ "Wcomment",                   &warns.nestcomment,    true },
	{ "Wendif-labels",              &warns.endiflabels,    true },
	{ "Werror",                     &mode.werror,          true },
	{ "Wno-comment",                &warns.nestcomment,    false },
	{ "Wno-endif-labels",           &warns.endiflabels,    false },
	{ "Wno-error",                  &mode.werror,          false },
	{ "Wno-undef",                  &warns.undef,          false },
	{ "Wno-unused-macros",          &warns.unused,         false },
	{ "Wundef",                     &warns.undef,          true },
	{ "Wunused-macros",             &warns.unused,         true },
	{ "fdollars-in-identifiers",    &mode.input_allow_dollars,     true },
	{ "fno-dollars-in-identifiers", &mode.input_allow_dollars,     false },
	{ "nostdinc",                   &mode.do_stdinc,               false },
	{ "traditional",		&traditional,    	       true },
	{ "undef",                      &mode.do_stddef,               false },
};
static const unsigned num_flag_options = HOWMANY(flag_options);

static const struct act_option act_options[] = {
	{ "H",         commandline_H },
	{ "M",         commandline_M },
	{ "MD",        commandline_MD },
	{ "MM",        commandline_MM },
	{ "MMD",       commandline_MMD },
	{ "Wall",      commandline_wall },
	{ "Wno-all",   commandline_wnoall },
	{ "dD",        commandline_dD },
	{ "dI",        commandline_dI },
	{ "dM",        commandline_dM },
	{ "dN",        commandline_dN },
	{ "w",         commandline_wnone },
};
static const unsigned num_act_options = HOWMANY(act_options);

static const struct prefix_option prefix_options[] = {
	{ "D",         commandline_def },
	{ "I",         commandline_addincpath_user },
	{ "U",         commandline_undef },
	{ "ftabstop=", commandline_tabstop },
	{ "std=",      commandline_setstd },
};
static const unsigned num_prefix_options = HOWMANY(prefix_options);

static const struct arg_option arg_options[] = {
	{ "MF",          commandline_setdependoutput },
	{ "MQ",          commandline_setdependtarget_quoted },
	{ "MT",          commandline_setdependtarget },
	{ "idirafter",   commandline_addincpath_late },
	{ "imacros",     commandline_addfile_nooutput },
	{ "include",     commandline_addfile_output },
	{ "iprefix",     commandline_setprefix },
	{ "iquote",      commandline_addincpath_quote },
	{ "iremap",      commandline_iremap },
	{ "isysroot",    commandline_isysroot },
	{ "isystem",     commandline_addincpath_system },
	{ "iwithprefix", commandline_addincpath_late_withprefix },
	{ "iwithprefixbefore", commandline_addincpath_user_withprefix },
	{ "x",           commandline_setlang },
};
static const unsigned num_arg_options = HOWMANY(arg_options);

static
bool
check_flag_option(const char *opt)
{
	unsigned i;
	int r;

	for (i=0; i<num_flag_options; i++) {
		r = strcmp(opt, flag_options[i].string);
		if (r == 0) {
			*flag_options[i].flag = flag_options[i].setto;
			return true;
		}
		if (r < 0) {
			break;
		}
	}
	return false;
}

static
bool
check_act_option(const char *opt)
{
	unsigned i;
	int r;

	for (i=0; i<num_act_options; i++) {
		r = strcmp(opt, act_options[i].string);
		if (r == 0) {
			act_options[i].func();
			return true;
		}
		if (r < 0) {
			break;
		}
	}
	return false;
}

static
bool
check_prefix_option(const struct place *p, char *opt)
{
	unsigned i, len;
	int r;

	for (i=0; i<num_prefix_options; i++) {
		len = strlen(prefix_options[i].string);
		r = strncmp(opt, prefix_options[i].string, len);
		if (r == 0) {
			prefix_options[i].func(p, opt + len);
			return true;
		}
		if (r < 0) {
			break;
		}
	}
	return false;
}

static
bool
check_arg_option(const char *opt, const struct place *argplace, char *arg)
{
	unsigned i;
	int r;

	for (i=0; i<num_arg_options; i++) {
		r = strcmp(opt, arg_options[i].string);
		if (r == 0) {
			if (arg == NULL) {
				warnx("Option -%s requires an argument", opt);
				die();
			}
			arg_options[i].func(argplace, arg);
			return true;
		}
		if (r < 0) {
			break;
		}
	}
	return false;
}

DEAD static
void
usage(const char *argv0)
{
	const char *progname;

	progname = strrchr(argv0, '/');
	progname = progname == NULL ? argv0 : progname + 1;

	fprintf(stderr, "Usage: %s [options] [infile [outfile]]\n", progname);
	fprintf(stderr, "Common options:\n");
	fprintf(stderr, "   -C               Retain comments\n");
	fprintf(stderr, "   -Dmacro[=def]    Predefine macro\n");
	fprintf(stderr, "   -Idir            Add to include path\n");
	fprintf(stderr, "   -M               Issue depend info\n");
	fprintf(stderr, "   -MD              Issue depend info and output\n");
	fprintf(stderr, "   -MM              -M w/o system headers\n");
	fprintf(stderr, "   -MMD             -MD w/o system headers\n");
	fprintf(stderr, "   -nostdinc        Drop default include path\n");
	fprintf(stderr, "   -Umacro          Undefine macro\n");
	fprintf(stderr, "   -undef           Undefine everything\n");
	fprintf(stderr, "   -Wall            Enable all warnings\n");
	fprintf(stderr, "   -Werror          Make warnings into errors\n");
	fprintf(stderr, "   -w               Disable all warnings\n");
	die();
}

////////////////////////////////////////////////////////////
// exit and cleanup

static struct stringarray freestrings;

static
void
init(void)
{
	stringarray_init(&freestrings);

	incpath_init();
	commandline_macros_init();
	commandline_files_init();

	place_init();
	files_init();
	directive_init();
	macros_init();
}

static
void
cleanup(void)
{
	unsigned i, num;

	macros_cleanup();
	directive_cleanup();
	files_cleanup();
	place_cleanup();

	commandline_files_cleanup();
	commandline_macros_cleanup();
	incpath_cleanup();

	num = stringarray_num(&freestrings);
	for (i=0; i<num; i++) {
		dostrfree(stringarray_get(&freestrings, i));
	}
	stringarray_setsize(&freestrings, 0);
	stringarray_cleanup(&freestrings);
}

void
die(void)
{
	cleanup();
	exit(EXIT_FAILURE);
}

void
freestringlater(char *s)
{
	stringarray_add(&freestrings, s, NULL);
}

////////////////////////////////////////////////////////////
// main

int
main(int argc, char *argv[])
{
	const char *inputfile = NULL;
	const char *outputfile = NULL;
	struct place cmdplace;
	int i;

	init();

	for (i=1; i<argc; i++) {
		if (argv[i][0] != '-') {
			break;
		}
		place_setcommandline(&cmdplace, i, 1);
		if (check_flag_option(argv[i]+1)) {
			continue;
		}
		if (check_act_option(argv[i]+1)) {
			continue;
		}
		if (check_prefix_option(&cmdplace, argv[i]+1)) {
			continue;
		}
		place_setcommandline(&cmdplace, i+1, 1);
		if (check_arg_option(argv[i]+1, &cmdplace, argv[i+1])) {
			i++;
			continue;
		}
		usage(argv[0]);
	}
	if (i < argc) {
		inputfile = argv[i++];
	}
	if (i < argc) {
		outputfile = argv[i++];
	}
	if (i < argc) {
		usage(argv[0]);
	}

	mode.output_file = outputfile;

	loadincludepath();
	apply_builtin_macros();
	apply_commandline_macros();
	read_commandline_files();
	place_setnowhere(&cmdplace);
	file_readabsolute(&cmdplace, inputfile);

	cleanup();
	if (complain_failed()) {
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}