/*
 * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
 *
 * Licensed under the GPL v3, see https://www.gnu.org/licenses/gpl-3.0.html
 */

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <time.h>

#include "lsg.h"
#include "lopsub-internal.h"
#include "lopsub.h"

/*
 * To stringify the result of the *expansion* of a macro argument, we have to
 * use two levels of macros.
 */
#define LLS_STRINGIFY_EXPAND(_arg) LLS_STRINGIFY(_arg)
#define LLS_STRINGIFY(_arg) #_arg
#define LLS_ABI_VERSION_VAR_STRING LLS_STRINGIFY_EXPAND(LLS_ABI_VERSION_VAR)

int yylex(void);

static void gen_license_header(FILE *out, const char *cmdline, const char *pfx,
		const char *sfx)
{
	fprintf(out, "%sautogenerated by lopsubgen version %s%s\n",
		pfx, lls_version(), sfx);
	if (cmdline) {
		fprintf(out, "%scommand line:", pfx);
		fprintf(out, " %s", cmdline);
		fprintf(out, "%s\n", sfx);
	}
	fprintf(out, "%sPublic domain, no copyright claims.%s\n",
		pfx, sfx);
}

static void inplace_sanitize(char *src)
{
	for (; *src; src++)
		if (*src == '-')
			*src = '_';
}

static void inplace_toupper(char *src)
{
	for (; *src; src++)
		*src = toupper((int)*src);
}

static void lsg_init_name(struct lsg_name *name)
{
	assert(name->orig);
	name->sanitized = strdup(name->orig);
	inplace_sanitize(name->sanitized);
	name->capitalized = strdup(name->sanitized);
	inplace_toupper(name->capitalized);
}

static void print_tabs(int num, FILE *out)
{
	while (--num >= 0)
		fprintf(out, "\t");
}

static void format_c_text(const char *text, const char *member, int indent,
		FILE *out)
{
	int i;

	if (!text)
		return;
	print_tabs(indent, out);
	fprintf(out, ".%s = (char []) {", member);
	for (i = 0; text[i]; i++) {
		if ((i % 8) == 0) {
			fprintf(out, "\n");
			print_tabs(indent + 1, out);
		}
		fprintf(out, "0x%02x,", (unsigned char)text[i]);
	}
	fprintf(out, "0x00\n");
	print_tabs(indent, out);
	fprintf(out, "},\n");
}

static void format_flags(struct lsg_option *opt, FILE *out)
{
	fprintf(out, "\t\t\t\t\t.flags = 0");
	if (opt->multiple)
		fprintf(out, " | LLS_MULTIPLE");
	if (opt->required)
		fprintf(out, " | LLS_REQUIRED");
	if (opt->ignored)
		fprintf(out, " | LLS_IGNORED");
	if (opt->default_val)
		fprintf(out, " | LLS_HAS_DEFAULT");
	fprintf(out, ",\n");
}

static int string_literal(const char *src, FILE *out)
{
	int len = 0;

	len += fprintf(out, "\"");
	while (src && *src) {
		if (*src == '"' || *src == '\\')
			len += fprintf(out, "\\");
		len += fprintf(out, "%c", *src);
		src++;
	}
	len += fprintf(out, "\"");
	return len;
}

static void format_man_text(FILE *out, bool bol, const char *text)
{
	int i;

	if (!text)
		return;
	for (i = 0; text[i]; i++) {
		switch (text[i]) {
		case '\n':
			fprintf(out, "\n");
			bol = true;
			continue;
		case '.':
		case '\'':
			if (bol)
				fprintf(out, "\\&");
			break;
		case '\\':
			fprintf(out, "\\");
			break;
		case ' ':
		case '\t':
			if (bol)
				continue;
		}
		bol = false;
		fprintf(out, "%c", text[i]);
	}
}

static int format_option_arg(const struct lsg_option *opt, FILE *out,
		bool man_format)
{
	char *typestr = opt->typestr? opt->typestr : "val";
	int arg_info, len = 0;

	if (opt->ignored)
		return 0;
	if (!opt->arg_info)
		arg_info = 0;
	else if (strcmp(opt->arg_info, "LLS_NO_ARGUMENT") == 0)
		arg_info = 0;
	else if (strcmp(opt->arg_info, "LLS_OPTIONAL_ARGUMENT") == 0)
		arg_info = -1;
	else if (strcmp(opt->arg_info, "LLS_REQUIRED_ARGUMENT") == 0)
		arg_info = 1;
	else
		assert(0);

	if (arg_info == -1)
		len += fprintf(out, "[");
	if (arg_info != 0) {
		len += fprintf(out, "=<");
		if (man_format)
			len += fprintf(out, "%s", typestr);
		else {
			len += fprintf(out, "\"");
			len += string_literal(typestr, out);
			len += fprintf(out, "\"");
		}
		fprintf(out, ">");
	}
	if (arg_info == -1)
		len += fprintf(out, "]");
	return len;
}

static void format_synopsis(const struct lsg_command *cmd, FILE *out,
		bool man_format)
{
	int j, len;

	if (man_format)
		fprintf(out, "\\&");
	if (cmd->synopsis) {
		if (man_format)
			format_man_text(out, true, cmd->synopsis);
		else {
			string_literal(cmd->synopsis, out);
			fprintf(out, ",\n");
		}
		return;
	}
	if (!man_format)
		fprintf(out, "\"");
	len = strlen(cmd->name.orig) + 8;
	for (j = 0; j < cmd->num_options; j++) {
		struct lsg_option *opt = cmd->options + j;
		if (opt->ignored)
			continue;
		if (j > 0) {
			if (!man_format && len > 70) {
				fprintf(out, "\\n\"\n\t\t\t\t\"       ");
				len = 8;
			} else
				len += fprintf(out, " ");
		}
		if (!opt->required)
			len += fprintf(out, "[");
		len += fprintf(out, "--%s", opt->name.orig);
		len += format_option_arg(opt, out, man_format);
		if (!opt->required)
			len += fprintf(out, "]");
	}
	if (cmd->non_opts_name) {
		len += strlen(cmd->non_opts_name);
		if (!man_format && len > 70)
			fprintf(out, "\\n\"\n\t\t\t\t\"      ");
		if (cmd->num_options > 0)
			fprintf(out, " [--]");
		fprintf(out, " %s", cmd->non_opts_name);
	}
	if (!man_format)
		fprintf(out, "\",");
}

static void gen_c_options(const struct lsg_command *cmd, FILE *out)
{
	int i, j;

	if (cmd->num_options == 0)
		return;
	fprintf(out, "\t\t.options = (struct lls_option[]) {\n");
	for (j = 0; j < cmd->num_options; j++) {
		struct lsg_option *opt = cmd->options + j;
		fprintf(out, "\t\t\t{\n");
		fprintf(out, "\t\t\t\t.name = \"%s\",\n",
			opt->name.orig);
		if (opt->short_opt)
			fprintf(out, "\t\t\t\t.short_opt = '%c',\n",
				opt->short_opt);
		if (opt->summary) {
			fprintf(out, "\t\t\t\t.summary = ");
			string_literal(opt->summary, out);
			fprintf(out, ",\n");
		}
		if (opt->arg_info)
			fprintf(out, "\t\t\t\t.arg_info = %s,\n",
				opt->arg_info);
		if (opt->arg_type)
			fprintf(out, "\t\t\t\t.arg_type = %s,\n",
				opt->arg_type);
		if (opt->typestr) {
			fprintf(out, "\t\t\t\t.typestr = ");
			string_literal(opt->typestr, out);
			fprintf(out, ",\n");
		}
		fprintf(out, "\t\t\t\t.values = ");
		if (opt->values) {
			fprintf(out, "(union lls_val *)(union lls_val[]) {\n");
			for (i = 0; i < opt->num_values; i++)
				fprintf(out, "\t\t\t\t\t{.string_val = %s},\n",
					opt->value_literals[i]);
			fprintf(out, "\t\t\t\t\t{.string_val = NULL}\n");
			fprintf(out, "\t\t\t\t},\n");
		} else
			fprintf(out, "NULL,\n");
		format_flags(opt, out);
		if (opt->default_val) {
			bool string = strcmp(opt->arg_type, "LLS_STRING") == 0;
			fprintf(out, "\t\t\t\t.default_val = {");
			if (string) {
				if (opt->values)
					fprintf(out, ".uint32_val = ");
				else {
					fprintf(out, ".string_val = ");
					string_literal(opt->default_val, out);
				}
			} else if (strcmp(opt->arg_type, "LLS_INT32") == 0)
				fprintf(out, ".int32_val = ");
			else if (strcmp(opt->arg_type, "LLS_UINT32") == 0)
				fprintf(out, ".uint32_val = ");
			else if (strcmp(opt->arg_type, "LLS_INT64") == 0)
				fprintf(out, ".int64_val = ");
			else if (strcmp(opt->arg_type, "LLS_UINT64") == 0)
				fprintf(out, ".uint64_val = ");
			if (!string || opt->values)
				fprintf(out, "%s", opt->default_val);
			fprintf(out, "},\n");
		}
		format_c_text(opt->help, "help", 5, out);
		fprintf(out, "\t\t\t},\n");
	}
	fprintf(out, "\t\t\t{\n\t\t\t\t\t.name = NULL\n\t\t\t}\n");
	fprintf(out, "\t\t}\n");
}

static void format_command(const struct lsg_command *cmd, FILE *out)
{
	if (!cmd->name.orig) {
		fprintf(out, "{.name = NULL}\n");
		return;
	}
	fprintf(out, "{\n\t\t.name = \"%s\",\n", cmd->name.orig);
	fprintf(out, "\t\t.purpose = ");
	string_literal(cmd->purpose, out);
	fprintf(out, ",\n");
	format_c_text(cmd->description, "description", 3, out);
	if (cmd->non_opts_name || cmd->synopsis)
		fprintf(out, "\t\t.non_opts_name = \"%s\",\n",
			cmd->non_opts_name? cmd->non_opts_name : "");
	fprintf(out, "\t\t.synopsis = ");
	format_synopsis(cmd, out, false);
	format_c_text(cmd->closing, "closing", 3, out);
	fprintf(out, "\n\t\t.user_data = &lsg_%s_com_%s_user_data,\n",
		suite.name.sanitized, cmd->name.sanitized);
	fprintf(out, "\t\t.num_options = %d,\n", cmd->num_options);
	gen_c_options(cmd, out);
	fprintf(out, "\t}");
}

static void format_user_data(const struct lsg_command *cmd, FILE *out)
{
	if (!cmd->name.orig)
		return;
	fprintf(out, "extern const void *lsg_%s_com_%s_user_data "
		"__attribute__ ((weak));\n",
		suite.name.sanitized,
		cmd->name.sanitized
	);
}

static void gen_c(const char *outpath, const char *cmdline)
{
	int i;

	FILE *out = fopen(outpath, "w");
	if (!out) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}
	gen_license_header(out, cmdline, "/* ", " */");
	fprintf(out, "#include <stdlib.h>\n");
	fprintf(out, "#include <inttypes.h>\n");
	fprintf(out, "#include <lopsub-internal.h>\n");
	fprintf(out, "#include <lopsub.h>\n");
	fprintf(out, "__attribute__ ((unused)) "
		"static const unsigned *abi_version = &%s;\n",
		LLS_ABI_VERSION_VAR_STRING);
	fprintf(out, "#if LLS_ABI_VERSION - %d\n", LLS_ABI_VERSION);
	fprintf(out, "#error: \"ABI version mismatch: header version "
		"differs from lopsubgen version\"\n");
	fprintf(out, "#endif\n");
	for (i = 0; i <= suite.num_subcommands; i++)
		format_user_data(suite.commands + i, out);
	fprintf(out, "static const struct lls_suite the_%s_suite = {\n",
		suite.name.sanitized);
	fprintf(out, "\t.name = \"%s\",\n", suite.name.orig);
	if (suite.caption)
		fprintf(out, "\t.caption = \"%s\",\n", suite.caption);
	fprintf(out, "\t.num_subcommands = %d,\n", suite.num_subcommands);
	fprintf(out, "\t.commands = (struct lls_command[]) {\n");
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		fprintf(out, "%s", i? ", " : "\t\t");
		format_command(cmd, out);
	}
	fprintf(out, ", {\n\t\t\t.name = NULL\n\t\t}\n");
	fprintf(out, "\t}\n");
	fprintf(out, "};\n");
	fprintf(out, "const struct lls_suite *%s_suite = &the_%s_suite;\n",
		suite.name.sanitized, suite.name.sanitized);
	fclose(out);
}

static inline bool has_arg(struct lsg_option *opt)
{
	if (!opt->arg_info)
		return false;
	return strcmp(opt->arg_info, "LLS_NO_ARGUMENT");
}

static void gen_header(const char *outpath, const char *cmdline)
{
	int i, j, k;
	FILE *out = fopen(outpath, "w");
	char *name;

	if (!out) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}
	gen_license_header(out, cmdline, "/* ", " */");
	/* generate command enum */
	fprintf(out, "extern const struct lls_suite *%s_suite;\n",
		suite.name.sanitized);
	fprintf(out, "#define LSG_%s_SUBCOMMANDS \\\n", suite.name.capitalized);
	for (i = 1; i <= suite.num_subcommands; i++)
		fprintf(out, "\tLSG_%s_CMD(%s), \\\n", suite.name.capitalized,
			suite.commands[i].name.sanitized);
	fprintf(out, "\n");
	fprintf(out, "#define LSG_%s_COMMANDS \\\n", suite.name.capitalized);
	name = suite.commands[0].name.sanitized;
	fprintf(out, "\tLSG_%s_CMD(%s), \\\n", suite.name.capitalized,
		name? name : "SUPERCOMMAND_UNAVAILABLE");
	fprintf(out, "\tLSG_%s_SUBCOMMANDS\n", suite.name.capitalized);
	fprintf(out, "enum lsg_%s_command {\n", suite.name.sanitized);
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		char *name = cmd->name.capitalized;

		if (!name)
			name = "SUPERCOMMAND_UNAVAILABLE";
		fprintf(out, "\tLSG_%s_CMD_%s,\n", suite.name.capitalized, name);
	}
	fprintf(out, "};\n#define LSG_NUM_%s_SUBCOMMANDS %u\n", suite.name.capitalized,
		suite.num_subcommands);
	fprintf(out, "#define LSG_%s_AUX_INFOS \\\n", suite.name.capitalized);
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		char *ai = cmd->aux_info;
		if (!ai) {
			ai = suite.aux_info_default;
			if (!ai)
				ai = "0";
		}
		fprintf(out, "\t%s_AUX_INFO(%s) /* %s */ \\\n",
			suite.name.capitalized, ai, cmd->name.orig?
				cmd->name.orig : "NO_SUPERCOMMAND");
	}
	fprintf(out, "\n");
	/* generate one option enum per command */
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		if (!cmd->name.orig)
			continue;
		fprintf(out, "enum lsg_%s_%s_option {\n", suite.name.sanitized,
			cmd->name.sanitized);
		for (j = 0; j < cmd->num_options; j++) {
			struct lsg_option *opt = cmd->options + j;
			char suffix[20] = "";
			if (opt->ignored)
				sprintf(suffix, "%d", j);
			fprintf(out, "\tLSG_%s_%s_OPT_%s%s,\n",
				suite.name.capitalized,
				cmd->name.capitalized,
				opt->name.capitalized,
				suffix
			);
		}
		fprintf(out, "\tLSG_NUM_%s_%s_OPTIONS\n};\n",
			suite.name.capitalized, cmd->name.capitalized);

	}
	/* generate enumeration for options of type enum */
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		if (!cmd->name.orig)
			continue;
		for (j = 0; j < cmd->num_options; j++) {
			struct lsg_option *opt = cmd->options + j;
			if (!opt->values)
				continue;
			fprintf(out, "/* cmd %s, opt %s */\n", cmd->name.orig,
				opt->name.orig);
			fprintf(out, "enum {");
			for (k = 0; k < opt->num_values; k++)
				fprintf(out, "%s, ", opt->value_ids[k]);
			fprintf(out, "LSG_NUM_%s_%s_%s_VALUES};\n",
				suite.name.capitalized, cmd->name.capitalized,
				opt->name.capitalized);
		}
	}
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		if (!cmd->name.orig)
			continue;
		fprintf(out, "#define LSG_%s_%s_SHORT_OPTS ",
			suite.name.capitalized, cmd->name.capitalized);
		for (j = 0; j < cmd->num_options; j++) {
			struct lsg_option *opt = cmd->options + j;
			if (!opt->short_opt)
				continue;
			fprintf(out, "\"-%c%s\"%s", opt->short_opt,
				has_arg(opt)? "=" : "",
				j == cmd->num_options - 1? "" : ", ");
		}
		fprintf(out, "\n");

		fprintf(out, "#define LSG_%s_%s_LONG_OPTS ",
			suite.name.capitalized, cmd->name.capitalized);
		for (j = 0; j < cmd->num_options; j++) {
			struct lsg_option *opt = cmd->options + j;
			fprintf(out, "\"--%s%s\"%s", opt->name.orig,
				has_arg(opt)? "=" : "",
				j == cmd->num_options - 1? "" : ", ");
		}
		fprintf(out, "\n");
		fprintf(out, "#define LSG_%s_%s_OPTS "
			"LSG_%s_%s_SHORT_OPTS, LSG_%s_%s_LONG_OPTS\n",
			suite.name.capitalized, cmd->name.capitalized,
			suite.name.capitalized, cmd->name.capitalized,
			suite.name.capitalized, cmd->name.capitalized
		);
	}
	fclose(out);
}

static void check_option(struct lsg_option *opt)
{
	if (!opt->arg_info || !strcmp(opt->arg_info, "no_arg"))
		return;
	if (opt->arg_type && strcmp(opt->arg_type, "none"))
		return;
	fprintf(stderr, "option '%s': inconsistent arg_type/arg_info\n",
		opt->name.orig);
	exit(EXIT_FAILURE);
}

static void sanity_check(void)
{
	int i, j;

	if (suite.num_subcommands == 0 && !suite.commands) {
		fprintf(stderr, "no (sub)commands defined\n");
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		if (!cmd->name.orig)
			continue;
		for (j = 0; j < cmd->num_options; j++)
			check_option(cmd->options + j);
	}
}

static void run_yylex(void)
{
	int i, j;

	yylex();
	if (!suite.name.orig)
		suite.name.orig = strdup("lopsubgen");
	sanity_check();
	lsg_init_name(&suite.name);
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		if (!cmd->name.orig)
			continue;
		lsg_init_name(&cmd->name);
		for (j = 0; j < cmd->num_options; j++)
			lsg_init_name(&cmd->options[j].name);
	}
	for (i = 0; i < suite.num_sections; i++)
		lsg_init_name(&suite.sections[i].name);
}

#ifdef STAGE1
int main(int argc, char **argv)
{
	run_yylex();
	gen_c("lopsubgen.lsg.c", NULL);
	gen_header("lopsubgen.lsg.h", NULL);
	return EXIT_SUCCESS;
}

#else /* STAGE1 */

#include "lopsubgen.lsg.h"

static char *get_output_path(const char *suffix, const char *arg,
		const struct lls_parse_result *lpr)
{
	size_t len;
	char *result, *output_dir;

	output_dir = OPT_STRING_VAL(OUTPUT_DIR, lpr);

	if (arg && arg[0] == '/') {
		result = strdup(arg);
		assert(result);
		return result;
	}
	if (arg) { /* relative path */
		len = strlen(output_dir) + strlen(arg) + 1;
		result = malloc(len + 1);
		assert(result);
		sprintf(result, "%s/%s", output_dir, arg);
		return result;
	}
	/* default: suite name plus suffix */
	len = strlen(output_dir) + 1 /* slash */ + strlen(suite.name.orig)
		+ 1 /* dot */ + 3 /* "lsg" */ + 1 /* dot */ + strlen(suffix);
	result = malloc(len + 1);
	assert(result);
	sprintf(result, "%s/%s.lsg.%s", output_dir, suite.name.orig, suffix);
	return result;
}

static void gen_man(struct lls_parse_result *lpr, const char *cmdline)
{
	int i;
	time_t t = 0;
	struct tm *tmp;
	FILE *out;
	char *outpath = get_output_path("man",
		OPT_STRING_VAL(GEN_MAN, lpr), lpr);

	out = fopen(outpath, "w");
	free(outpath);
	if (!out) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}
	gen_license_header(out, cmdline, ".\\\" ", "");
	if (suite.commands[0].name.orig) {
		char date[200];
		const char *version_string;
		if (!suite.date) {
			/*
			 * If the SOURCE_DATE_EPOCH environment variable
			 * contains a positive integer in the time_t range, use
			 * that instead of the current time. See:
			 * <https://reproducible-builds.org/specs/source-date-epoch/>
			 * for more information.
			 */
			char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
			if (source_date_epoch != NULL)
				t = strtoll(source_date_epoch, NULL, 10);
			if (t <= 0)
				t = time(NULL);
			tmp = gmtime(&t);
			if (tmp == NULL) {
				perror("gmtime");
				exit(EXIT_FAILURE);
			}
			if (strftime(date, sizeof(date), "%B %Y", tmp) == 0) {
				fprintf(stderr, "strftime returned 0\n");
				exit(EXIT_FAILURE);
			}
		}
		if (OPT_GIVEN(VERSION_STRING, lpr))
			version_string = OPT_STRING_VAL(VERSION_STRING, lpr);
		else
			version_string = suite.version_string?
				suite.version_string : "";
		fprintf(out, ".TH %s \"%s\" \"%s\" \"%s\" \"%s\"\n",
			suite.title? suite.title : suite.commands[0].name.orig,
			suite.mansect? suite.mansect : "1",
			suite.date? suite.date : date,
			version_string,
			suite.manual_title? suite.manual_title : "User commands"
		);
	}
	for (i = 0; i <= suite.num_subcommands; i++) {
		struct lsg_command *cmd = suite.commands + i;
		int opt_num;
		if (!cmd->name.orig)
			continue;
		if (i == 0) {
			fprintf(out, ".SH NAME\n");
			fprintf(out, ".B\n%s \\- ", cmd->name.orig);
			format_man_text(out, false, cmd->purpose);
		} else {
			if (i == 1 && suite.caption) {
				char *caption = strdup(suite.caption);
				inplace_toupper(caption);
				fprintf(out, ".SH %s\n.P\n", caption);
				free(caption);
			}
			if (i == 1 && suite.introduction)
				format_man_text(out, true, suite.introduction);
			fprintf(out, ".SS \n%s \\- ", cmd->name.orig);
			format_man_text(out, false, cmd->purpose);
		}
		fprintf(out, "\n.P\n");
		if (i == 0)
			fprintf(out, ".SH SYNOPSIS\n");
		else
			fprintf(out, "Usage: \n");
		fprintf(out, ".B %s\n", cmd->name.orig);
		format_synopsis(cmd, out, true);
		fprintf(out, "\n.P\n");
		if (cmd->description) {
			if (i == 0)
				fprintf(out, ".SH DESCRIPTION\n");
			format_man_text(out, true, cmd->description);
		}
		if (cmd->num_options > 0)
			if (i == 0)
				fprintf(out, ".SH OPTIONS\n");
		for (opt_num = 0; opt_num < cmd->num_options; opt_num++) {
			struct lsg_option *opt = cmd->options + opt_num;

			if (opt->ignored) {
				fprintf(out, ".SS ");
				format_man_text(out, false, opt->summary);
				fprintf(out, "\n");
				if (opt->help)
					format_man_text(out, true, opt->help);
				continue;

			}
			fprintf(out, ".TP\n");
			if (opt->short_opt != '\0')
				fprintf(out, "\\fB\\-%c\\fR, ", opt->short_opt);
			fprintf(out, "\\fB\\-\\-%s\\fR", opt->name.orig);
			format_option_arg(opt, out, true /* man_format */);
			fprintf(out, "\n");
			format_man_text(out, true, opt->summary);
			fprintf(out, "\n");
			if (opt->values) {
				unsigned n, dflt;
				fprintf(out, ".IP\n");
				fprintf(out, "values:\n");
				dflt = opt->default_val?
					atoi(opt->default_val) : 0;
				for (n = 0; n < opt->num_values; n++) {
					if (n == dflt)
						fprintf(out, ".B ");
					fprintf(out, "%s%s\n", opt->values[n],
						n == opt->num_values - 1?
							"" : ",");
				}
			} else if (opt->default_val) {
				fprintf(out, ".IP\n");
				fprintf(out, "default: ");
				format_man_text(out, false, opt->default_val);
				fprintf(out, "\n.P\n");
			}
			if (opt->help) {
				fprintf(out, ".IP\n");
				format_man_text(out, true, opt->help);
			}
		}
		fprintf(out, ".PP\n");
		if (cmd->aux_info) {
			char *pfx = suite.aux_info_prefix;
			fprintf(out, ".RS\n");
			if (pfx)
				fprintf(out, "%s ", pfx);
			fprintf(out, "%s\n.PP\n", cmd->aux_info);
			fprintf(out, ".RE\n");
		}
		if (cmd->closing)
			format_man_text(out, true, cmd->closing);
	}
	if (suite.conclusion)
		format_man_text(out, true, suite.conclusion);
	for (i = 0; i < suite.num_sections; i++) {
		struct lsg_section *sec = suite.sections + i;
		fprintf(out, "\n.P\n.SH \"%s\"\n", sec->name.capitalized);
		fprintf(out, "%s\n.P\n", sec->text);
	}
	fclose(out);
}

int main(int argc, char **argv)
{
	const struct lls_command *cmd = lls_cmd(0, lopsubgen_suite);
	char *errctx, *outpath, *cmdline;
	struct lls_parse_result *lpr;
	int i, ret;

	/* Make a copy of the command line because lls_parse() permutes argv[] */
	for (i = 0, ret = 0; argv[i]; i++)
		ret += strlen(argv[i]) + 1;
	cmdline = malloc(ret + 1);
	assert(cmdline);
	for (i = 0, ret = 0; argv[i]; i++)
		ret += sprintf(cmdline + ret, "%s ", argv[i]);
	cmdline[ret - 1] = '\0';

	ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
	if (ret < 0) {
		if (errctx)
			fprintf(stderr, "%s\n", errctx);
		fprintf(stderr, "%s\n", lls_strerror(-ret));
		return EXIT_FAILURE;
	}
	if (OPT_GIVEN(VERSION, lpr)) {
		printf("lopsubgen-%s\n", lls_version());
		return EXIT_SUCCESS;
	}
	if (OPT_GIVEN(HELP, lpr)) {
		char *help;
		if (OPT_GIVEN(HELP, lpr) > 1)
			help = lls_long_help(cmd);
		else
			help = lls_short_help(cmd);
		printf("%s", help);
		free(help);
		return EXIT_SUCCESS;
	}
	run_yylex();
	if (OPT_GIVEN(GEN_C, lpr)) {
		outpath = get_output_path("c",
			OPT_STRING_VAL(GEN_C, lpr), lpr);
		gen_c(outpath, cmdline);
		free(outpath);
	}
	if (OPT_GIVEN(GEN_HEADER, lpr)) {
		outpath = get_output_path("h",
			OPT_STRING_VAL(GEN_HEADER, lpr), lpr);
		gen_header(outpath, cmdline);
		free(outpath);
	}
	if (OPT_GIVEN(GEN_MAN, lpr))
		gen_man(lpr, cmdline);
	return EXIT_SUCCESS;
}
#endif /* STAGE1 */
