/*
 * config - used to set up option files for programs with to many conditional
 *	compilation defines.
 *
 *	copyright (c) 1989, Mike Meyer
 *
 * Usage: config <configfile>
 *	.h files are generated in /<configfile> setting the options
 *	as specified in <configfile>. But first, config.options is
 *	read to find out what options you can & can't set for the
 *	program being configed. See config.doc and make-config.doc
 *	for details.
 */

/*
 * There are three magic globals - options, required and actions. required
 *	keeps track of the options that you _have_ to specify, and whether
 *	it's seen them or not. options keeps track of the options you can
 *	specify, and the current value. The syntax for both is the
 *	same: X.<name> holds the value of option <name>, or whether
 *	required.<name> has been seen or not. Since there's no easy
 *	way to walk through all the members on a stem (yet), we also
 *	have to keep track of the names. They are stored in X.1 through
 *	X.N, where there are currently N names in X. Just to keep everything
 *	in one place, N is kept in X.0. Actions keeps track of any actions
 *	that have been specified. Since they have no names, the actions
 *	themselves are tracked in actions.N, with actions.0 holding the
 *	number of actions specified.
 */

/*
 * Main routine - initialize the globals, read in the base config file,
 *	read in and apply the options the user selects, then write the
 *	appropriate include files out.
 */

arg system
undefined = '/\/\.* '		/* value for options not in .options files */
namerequired = '/\...* '	/* value for required options not yet used */
options. = undefined 		/* none are defined yet */
required. = undefined		/* Haven't seen it yet */

options.0 = 0			/* No options yet */
required.0 = 0			/* and no required options */
actions.0 = 0			/* and no actions */

if system = "" then do
	say "usage: config CONFIGFILE"
	exit 10
	end

if ~open(basefile, 'config.options', 'Read') then do
	say "Can't find config base"
	exit 20
	end
call buildoptions basefile
call close basefile

if ~open(configfile, system, 'Read') then do
	say "Can't find config file" system
	exit 20
	end
call applyoptions configfile
call close configfile

call dumpoptions system
exit 0

/*
 * buildoptions - given an input file, it builds the option & required data
 *	structure for that file.
 */
buildoptions: procedure expose options. required. actions. namerequired
	arg	optfile

	do forever
		call readline optfile
		if eof(optfile) & name = "" then leave
		if name == 'action' then do
			new = getnextaction()
			actions.new = value
			end
		else if value ~= 'required' then do
			new = getnextoption()
			options.new = name
			if value = 'option' then value = 'off'
			options.name = value
			end
		else do
			new = getnextrequired()
			required.new = name
			required.name = namerequired
			end
		end
	return

/*
 * applyoptions - given a file, we apply each option line to the existing
 *	database.
 */
applyoptions: procedure expose options. required. actions. undefined namerequired
	arg	optfile

	requireddone = 0
	do forever
		call readline optfile
		if eof(optfile) & name = "" then leave
		if name = 'option' then do
			name = value
			value = 'on'
			end
		if options.name ~= undefined then do
			if ~requireddone then do
				requireddone = checkrequire()
				if ~requireddone then do
					say "Option" name "specified before required options"
					exit 10
					end
				end
			options.name = value
			end
		else if required.name ~= undefined then do
			if required.name ~= namerequired then do
				say 'Required' name 'seen twice.'
				exit 10
				end
			required.name = value
			if ~open(reqfile, value || '.options', 'Read') then do
				say "Invalid value:" value "for option:" name
				exit 20
				end
			call buildoptions reqfile
			call close reqfile
			end
		else say 'Unkown option' name 'ignored.'
		end
	return

/*
 * dumpoptions - output the include files as specified by this config file.
 */
dumpoptions: procedure expose options. required. actions.
	arg	conf


	if ~exists('/' || conf) then 'makedir /' || conf

	do i=1 to options.0
		name = options.i
		if options.name = 'off' then out = ""
		else if options.name = 'on' then out = "#define" upper(name)
		else out = "#define" upper(name) options.name
		call dumpinc conf, name, out
		end

	do i=1 to required.0
		name = required.i
		out = "#define" upper(required.name)
		call dumpinc conf, name, out
		end

	'cd /' || conf
	do i=1 to actions.0
		actions.i
		end
	return

/*
 * readline - read a line in, and put the appropriate parts in the globals
 *	'name' and 'value'.
 */
readline: procedure expose name value
	arg file

	do forever
		line = translate(readln(file), " ", "	")	/* tabs */
		parse var line name value "# "
		if name ~= "#" & name ~= "" then leave
		if eof(file) then do
			name = ""
			return
			end
		end
	value = strip(value)
	return
/*
 * getnextoption - returns the number for the next free option.
 */
getnextoption: procedure expose options.0

	options.0 = options.0 + 1
	return options.0

/*
 * getnextrequired - returns the number for the next free required.
 */
getnextrequired: procedure expose required.0

	required.0 = required.0 + 1
	return required.0

/*
 * getnextaction - returns the number for the next free action.
 */
getnextaction: procedure expose actions.0

	actions.0 = actions.0 + 1
	return actions.0

/*
 * checkrequire - return 1 if all required options have been seen, 0
 *	otherwise.
 */
checkrequire: procedure expose required. namerequired

	do i=1 to required.0
		name = required.i
		if required.name = namerequired then return 0
		end
	return 1

/*
 * dumpinc - creates a new include file, but only if it's different from
 *	the current one.
 */
dumpinc: procedure
	parse arg dir, file, line

	name = '/' || dir || '/' || file || ".h"
	if open(incfile, name, 'Read') then do
		old = readln(incfile)
		call close incfile
		if old = line then return
		end
	if ~open(incfile, name, 'Write') then do
		say "Can't create include file" name
		return
		end
	call writeln incfile, line
	call close incfile
	return
