/*
 * Copyright (c) 2001-2003
 *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
 *	All rights reserved.
 *
 * Copyright (c) 2004-2006,2018
 *	Hartmut Brandt.
 *	All rights reserved.
 *
 * Author: Harti Brandt <harti@freebsd.org>
 *
 * 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 AUTHOR 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 AUTHOR 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.
 *
 * $Begemot: gensnmptree.c 383 2006-05-30 07:40:49Z brandt_h $
 *
 * Generate OID table from table description.
 *
 * Syntax is:
 * ---------
 * file := top | top file
 *
 * top := tree | typedef | include
 *
 * tree := head elements ')'
 *
 * entry := head ':' index STRING elements ')'
 *
 * leaf := head type STRING ACCESS ')'
 *
 * column := head type ACCESS ')'
 *
 * type := BASETYPE | BASETYPE '|' subtype | enum | bits
 *
 * subtype := STRING
 *
 * enum := ENUM '(' value ')'
 *
 * bits := BITS '(' value ')'
 *
 * value := optminus INT STRING | optminus INT STRING value
 *
 * optminus := '-' | EMPTY
 *
 * head := '(' INT STRING
 *
 * elements := EMPTY | elements element
 *
 * element := tree | leaf | column
 *
 * index := type | index type
 *
 * typedef := 'typedef' STRING type
 *
 * include := 'include' filespec
 *
 * filespec := '"' STRING '"' | '<' STRING '>'
 */
#include <sys/types.h>
#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <inttypes.h>
#include <errno.h>
#ifdef HAVE_ERR_H
#include <err.h>
#endif
#include <sys/queue.h>
#include "support.h"
#include "asn1.h"
#include "snmp.h"
#include "snmpagent.h"

/*
 * Constant prefix for all OIDs
 */
static const asn_subid_t prefix[] = { 1, 3, 6 };
#define	PREFIX_LEN	(sizeof(prefix) / sizeof(prefix[0]))

u_int tree_size;
static const char *file_prefix = "";

/* if true generate local include paths */
static int localincs = 0;

/* if true print tokens */
static int debug;

static const char usgtxt[] = "\
Generate SNMP tables.\n\
usage: gensnmptree [-dEeFfhlt] [-I directory] [-i infile] [-p prefix]\n\
	    [name]...\n\
options:\n\
  -d		debug mode\n\
  -E		extract the named or all enums and bits only\n\
  -e		extract the named oids or enums\n\
  -F		generate functions for -E into a .c file\n\
  -f		generate functions for -E into the header\n\
  -h		print this info\n\
  -I directory	add directory to include path\n\
  -i ifile	read from the named file instead of stdin\n\
  -l		generate local include directives\n\
  -p prefix	prepend prefix to file and variable names\n\
  -t		generate a .def file\n\
";

/**
 * Program operation.
 */
enum op {
	/** generate the tree */
	OP_GEN,

	/** extract OIDs */
	OP_EXTRACT,

	/** print the parsed tree */
	OP_TREE,

	/** extract enums */
	OP_ENUMS,
};

/**
 * Which functions to create.
 */
enum gen_funcs {
	/** none */
	GEN_FUNCS_NONE,

	/** functions for header files */
	GEN_FUNCS_H,

	/** functions for C files */
	GEN_FUNCS_C,
};

/*
 * A node in the OID tree
 */
enum ntype {
	NODE_LEAF = 1,
	NODE_TREE,
	NODE_ENTRY,
	NODE_COLUMN
};

enum {
	FL_GET	= 0x01,
	FL_SET	= 0x02,
};

struct node;
TAILQ_HEAD(node_list, node);

struct node {
	enum ntype	type;
	asn_subid_t	id;	/* last element of OID */
	char		*name;	/* name of node */
	TAILQ_ENTRY(node) link;
	u_int		lno;	/* starting line number */
	u_int		flags;	/* allowed operations */

	union {
	  struct tree {
	    struct node_list subs;
	  }		tree;

	  struct entry {
	    uint32_t	index;	/* index for table entry */
	    char	*func;	/* function for tables */
	    struct node_list subs;
	    char	*subtypes[SNMP_INDEXES_MAX];
	  }		entry;

	  struct leaf {
	    enum snmp_syntax syntax;	/* syntax for this leaf */
	    char	*func;		/* function name */
	    char	*subtype;	/* subtype */
	  }		leaf;

	  struct column {
	    enum snmp_syntax syntax;	/* syntax for this column */
	    char	*subtype;	/* subtype */
	  }		column;
	}		u;
};

struct func {
	const char	*name;
	LIST_ENTRY(func) link;
};

static LIST_HEAD(, func) funcs = LIST_HEAD_INITIALIZER(funcs);

struct enums {
	const char	*name;
	long		value;
	TAILQ_ENTRY(enums) link;
};

struct type {
	const char	*name;
	const char	*from_fname;
	u_int		from_lno;
	u_int		syntax;
	int		is_enum;
	int		is_bits;
	TAILQ_HEAD(, enums) enums;
	LIST_ENTRY(type) link;
};

static LIST_HEAD(, type) types = LIST_HEAD_INITIALIZER(types);

static void report(const char *, ...) __dead2 __printflike(1, 2);
static void report_node(const struct node *, const char *, ...)
    __dead2 __printflike(2, 3);

/************************************************************
 *
 * Allocate memory and panic just in the case...
 */
static void *
xalloc(size_t size)
{
	void *ptr;

	if ((ptr = calloc(1, size)) == NULL)
		err(1, "allocing %zu bytes", size);

	return (ptr);
}

static char *
savestr(const char *s)
{

	if (s == NULL)
		return (NULL);
	return (strcpy(xalloc(strlen(s) + 1), s));
}

/************************************************************
 *
 * Input stack
 */
struct input {
	FILE		*fp;
	u_int		lno;
	char		*fname;
	char		*path;
	LIST_ENTRY(input) link;
};
static LIST_HEAD(, input) inputs = LIST_HEAD_INITIALIZER(inputs);
static struct input *input = NULL;

#define MAX_PATHS	100
static u_int npaths = 2;
static u_int stdpaths = 2;
static const char *paths[MAX_PATHS + 1] = {
	"/usr/share/snmp/defs",
	"/usr/local/share/snmp/defs",
	NULL
};

static int pbchar = -1;

static void
path_new(const char *path)
{
	if (npaths >= MAX_PATHS)
		report("too many -I directives");
	memmove(&paths[npaths - stdpaths + 1], &paths[npaths - stdpaths],
	    sizeof(path[0]) * stdpaths);
	paths[npaths - stdpaths] = savestr(path);
	npaths++;
}

static void
input_new(FILE *fp, const char *path, const char *fname)
{
	struct input *ip;

	ip = xalloc(sizeof(*ip));
	ip->fp = fp;
	ip->lno = 1;
	ip->fname = savestr(fname);
	ip->path = savestr(path);
	LIST_INSERT_HEAD(&inputs, ip, link);

	input = ip;
}

static void
input_close(void)
{

	if (input == NULL)
		return;
	fclose(input->fp);
	free(input->fname);
	free(input->path);
	LIST_REMOVE(input, link);
	free(input);

	input = LIST_FIRST(&inputs);
}

static FILE *
tryopen(const char *path, const char *fname)
{
	char *fn;
	FILE *fp;

	if (path == NULL)
		fn = savestr(fname);
	else {
		fn = xalloc(strlen(path) + strlen(fname) + 2);
		sprintf(fn, "%s/%s", path, fname);
	}
	fp = fopen(fn, "r");
	free(fn);
	return (fp);
}

static void
input_fopen(const char *fname, int loc)
{
	FILE *fp;
	char *path;
	u_int p;

	if (fname[0] == '/') {
		if ((fp = tryopen(NULL, fname)) != NULL) {
			input_new(fp, NULL, fname);
			return;
		}

	} else {
		if (loc) {
			if (input == NULL)
				path = NULL;
			else
				path = input->path;

			if ((fp = tryopen(path, fname)) != NULL) {
				input_new(fp, NULL, fname);
				return;
			}
		}

		for (p = 0; paths[p] != NULL; p++)
			if ((fp = tryopen(paths[p], fname)) != NULL) {
				input_new(fp, paths[p], fname);
				return;
			}
	}
	report("cannot open '%s'", fname);
}

static int
tgetc(void)
{
	int c;

	if (pbchar != -1) {
		c = pbchar;
		pbchar = -1;
		return (c);
	}

	for (;;) {
		if (input == NULL)
			return (EOF);

		if ((c = getc(input->fp)) != EOF)
			return (c);

		input_close();
	}
}

static void
tungetc(int c)
{

	if (pbchar != -1)
		abort();
	pbchar = c;
}

/************************************************************
 *
 * Parsing input
 */
enum tok {
	TOK_EOF = 0200,	/* end-of-file seen */
	TOK_NUM,	/* number */
	TOK_STR,	/* string */
	TOK_ACCESS,	/* access operator */
	TOK_TYPE,	/* type operator */
	TOK_ENUM,	/* enum token (kind of a type) */
	TOK_TYPEDEF,	/* typedef directive */
	TOK_DEFTYPE,	/* defined type */
	TOK_INCLUDE,	/* include directive */
	TOK_FILENAME,	/* filename ("foo.bar" or <foo.bar>) */
	TOK_BITS,	/* bits token (kind of a type) */
};

static const struct {
	const char *str;
	enum tok tok;
	u_int val;
} keywords[] = {
	{ "GET", TOK_ACCESS, FL_GET },
	{ "SET", TOK_ACCESS, FL_SET },
	{ "NULL", TOK_TYPE, SNMP_SYNTAX_NULL },
	{ "INTEGER", TOK_TYPE, SNMP_SYNTAX_INTEGER },
	{ "INTEGER32", TOK_TYPE, SNMP_SYNTAX_INTEGER },
	{ "UNSIGNED32", TOK_TYPE, SNMP_SYNTAX_GAUGE },
	{ "OCTETSTRING", TOK_TYPE, SNMP_SYNTAX_OCTETSTRING },
	{ "IPADDRESS", TOK_TYPE, SNMP_SYNTAX_IPADDRESS },
	{ "OID", TOK_TYPE, SNMP_SYNTAX_OID },
	{ "TIMETICKS", TOK_TYPE, SNMP_SYNTAX_TIMETICKS },
	{ "COUNTER", TOK_TYPE, SNMP_SYNTAX_COUNTER },
	{ "GAUGE", TOK_TYPE, SNMP_SYNTAX_GAUGE },
	{ "COUNTER64", TOK_TYPE, SNMP_SYNTAX_COUNTER64 },
	{ "ENUM", TOK_ENUM, SNMP_SYNTAX_INTEGER },
	{ "BITS", TOK_BITS, SNMP_SYNTAX_OCTETSTRING },
	{ "typedef", TOK_TYPEDEF, 0 },
	{ "include", TOK_INCLUDE, 0 },
	{ NULL, 0, 0 }
};

/* arbitrary upper limit on node names and function names */
#define	MAXSTR	1000
static char	str[MAXSTR];
static u_long	val;		/* integer values */
static int	saved_token = -1;

/*
 * Report an error and exit.
 */
static void
report(const char *fmt, ...)
{
	va_list ap;
	int c;

	va_start(ap, fmt);
	fprintf(stderr, "line %u: ", input->lno);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	fprintf(stderr, "context: \"");
	while ((c = tgetc()) != EOF && c != '\n')
		fprintf(stderr, "%c", c);
	fprintf(stderr, "\n");
	va_end(ap);
	exit(1);
}
static void
report_node(const struct node *np, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	fprintf(stderr, "line %u, node %s: ", np->lno, np->name);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	va_end(ap);
	exit(1);
}

/*
 * Return a fresh copy of the string constituting the current token.
 */
static char *
savetok(void)
{
	return (savestr(str));
}

/*
 * Get the next token from input.
 */
static int
gettoken_internal(void)
{
	int c;
	struct type *t;

	if (saved_token != -1) {
		c = saved_token;
		saved_token = -1;
		return (c);
	}

  again:
	/*
	 * Skip any whitespace before the next token
	 */
	while ((c = tgetc()) != EOF) {
		if (c == '\n')
			input->lno++;
		if (!isspace(c))
			break;
	}
	if (c == EOF)
		return (TOK_EOF);
	if (!isascii(c))
		report("unexpected character %#2x", (u_int)c);

	/*
	 * Skip comments
	 */
	if (c == '#') {
		while ((c = tgetc()) != EOF) {
			if (c == '\n') {
				input->lno++;
				goto again;
			}
		}
		report("unexpected EOF in comment");
	}

	/*
	 * Single character tokens
	 */
	if (strchr("():|-", c) != NULL)
		return (c);

	if (c == '"' || c == '<') {
		int end = c;
		size_t n = 0;

		val = 1;
		if (c == '<') {
			val = 0;
			end = '>';
		}

		while ((c = tgetc()) != EOF) {
			if (c == end)
				break;
			if (n == sizeof(str) - 1) {
				str[n++] = '\0';
				report("filename too long '%s...'", str);
			}
			str[n++] = c;
		}
		str[n++] = '\0';
		return (TOK_FILENAME);
	}

	/*
	 * Sort out numbers
	 */
	if (isdigit(c)) {
		size_t n = 0;
		str[n++] = c;
		while ((c = tgetc()) != EOF) {
			if (!isdigit(c)) {
				tungetc(c);
				break;
			}
			if (n == sizeof(str) - 1) {
				str[n++] = '\0';
				report("number too long '%s...'", str);
			}
			str[n++] = c;
		}
		str[n++] = '\0';
		sscanf(str, "%lu", &val);
		return (TOK_NUM);
	}

	/*
	 * So that has to be a string.
	 */
	if (isalpha(c) || c == '_') {
		size_t n = 0;
		str[n++] = c;
		while ((c = tgetc()) != EOF) {
			if (!isalnum(c) && c != '_' && c != '-') {
				tungetc(c);
				break;
			}
			if (n == sizeof(str) - 1) {
				str[n++] = '\0';
				report("string too long '%s...'", str);
			}
			str[n++] = c;
		}
		str[n++] = '\0';

		/*
		 * Keywords
		 */
		for (c = 0; keywords[c].str != NULL; c++)
			if (strcmp(keywords[c].str, str) == 0) {
				val = keywords[c].val;
				return (keywords[c].tok);
			}

		LIST_FOREACH(t, &types, link) {
			if (strcmp(t->name, str) == 0) {
				val = t->syntax;
				return (TOK_DEFTYPE);
			}
		}
		return (TOK_STR);
	}
	if (isprint(c))
		errx(1, "%u: unexpected character '%c'", input->lno, c);
	else
		errx(1, "%u: unexpected character 0x%02x", input->lno,
		    (u_int)c);
}
static int
gettoken(void)
{
	int tok = gettoken_internal();

	if (debug) {
		switch (tok) {

		  case TOK_EOF:
			fprintf(stderr, "EOF ");
			break;

		  case TOK_NUM:
			fprintf(stderr, "NUM(%lu) ", val);
			break;

		  case TOK_STR:
			fprintf(stderr, "STR(%s) ", str);
			break;

		  case TOK_ACCESS:
			fprintf(stderr, "ACCESS(%lu) ", val);
			break;

		  case TOK_TYPE:
			fprintf(stderr, "TYPE(%lu) ", val);
			break;

		  case TOK_ENUM:
			fprintf(stderr, "ENUM ");
			break;

		  case TOK_BITS:
			fprintf(stderr, "BITS ");
			break;

		  case TOK_TYPEDEF:
			fprintf(stderr, "TYPEDEF ");
			break;

		  case TOK_DEFTYPE:
			fprintf(stderr, "DEFTYPE(%s,%lu) ", str, val);
			break;

		  case TOK_INCLUDE:
			fprintf(stderr, "INCLUDE ");
			break;

		  case TOK_FILENAME:
			fprintf(stderr, "FILENAME ");
			break;

		  default:
			if (tok < TOK_EOF) {
				if (isprint(tok))
					fprintf(stderr, "'%c' ", tok);
				else if (tok == '\n')
					fprintf(stderr, "\n");
				else
					fprintf(stderr, "%02x ", tok);
			} else
				abort();
			break;
		}
	}
	return (tok);
}

/**
 * Pushback a token
 */
static void
pushback(enum tok tok)
{

	if (saved_token != -1)
		abort();
	saved_token = tok;
}

/*
 * Create a new type
 */
static struct type *
make_type(const char *s)
{
	struct type *t;

	t = xalloc(sizeof(*t));
	t->name = savestr(s);
	t->is_enum = 0;
	t->syntax = SNMP_SYNTAX_NULL;
	t->from_fname = savestr(input->fname);
	t->from_lno = input->lno;
	TAILQ_INIT(&t->enums);
	LIST_INSERT_HEAD(&types, t, link);

	return (t);
}

/*
 * Parse a type. We've seen the ENUM or type keyword already. Leave next
 * token.
 */
static u_int
parse_type(enum tok *tok, struct type *t, const char *vname, char **subtype)
{
	u_int syntax;
	struct enums *e;

	syntax = val;
	if (subtype != NULL)
		*subtype = NULL;

	if (*tok == TOK_ENUM || *tok == TOK_BITS) {
		if (t == NULL && vname != NULL) {
			t = make_type(vname);
			t->is_enum = (*tok == TOK_ENUM);
			t->is_bits = (*tok == TOK_BITS);
			t->syntax = syntax;
		}
		if (gettoken() != '(')
			report("'(' expected after ENUM");

		if ((*tok = gettoken()) == TOK_EOF)
			report("unexpected EOF in ENUM");
		do {
			e = NULL;
			if (t != NULL) {
				e = xalloc(sizeof(*e));
			}
			if (*tok == '-') {
				if ((*tok = gettoken()) == TOK_EOF)
					report("unexpected EOF in ENUM");
				e->value = -(long)val;
			} else
				e->value = val;

			if (*tok != TOK_NUM)
				report("need value for ENUM/BITS");
			if (gettoken() != TOK_STR)
				report("need string in ENUM/BITS");
			e->name = savetok();
			TAILQ_INSERT_TAIL(&t->enums, e, link);
			if ((*tok = gettoken()) == TOK_EOF)
				report("unexpected EOF in ENUM/BITS");
		} while (*tok != ')');
		*tok = gettoken();

	} else if (*tok == TOK_DEFTYPE) {
		*tok = gettoken();

	} else {
		if ((*tok = gettoken()) == '|') {
			if (gettoken() != TOK_STR)
				report("subtype expected after '|'");
			if (subtype != NULL)
				*subtype = savetok();
			*tok = gettoken();
		}
	}

	return (syntax);
}

/*
 * Parse the next node (complete with all subnodes)
 */
static struct node *
parse(enum tok tok)
{
	struct node *node;
	struct node *sub;
	u_int index_count;

	node = xalloc(sizeof(struct node));
	node->lno = input->lno;
	node->flags = 0;

	if (tok != '(')
		report("'(' expected at begin of node");
	if (gettoken() != TOK_NUM)
		report("node id expected after opening '('");
	if (val > ASN_MAXID)
		report("subid too large '%lu'", val);
	node->id = (asn_subid_t)val;
	if (gettoken() != TOK_STR)
		report("node name expected after '(' ID");
	node->name = savetok();

	if ((tok = gettoken()) == TOK_TYPE || tok == TOK_DEFTYPE ||
	    tok == TOK_ENUM || tok == TOK_BITS) {
		/* LEAF or COLUM */
		char *subtype;
		u_int syntax = parse_type(&tok, NULL, node->name, &subtype);

		if (tok == TOK_STR) {
			/* LEAF */
			node->type = NODE_LEAF;
			node->u.leaf.func = savetok();
			node->u.leaf.syntax = syntax;
			node->u.leaf.subtype = subtype;
			tok = gettoken();
		} else {
			/* COLUMN */
			node->type = NODE_COLUMN;
			node->u.column.syntax = syntax;
			node->u.column.subtype = subtype;
		}

		while (tok != ')') {
			if (tok != TOK_ACCESS)
				report("access keyword or ')' expected");
			node->flags |= (u_int)val;
			tok = gettoken();
		}

	} else if (tok == ':') {
		/* ENTRY */
		node->type = NODE_ENTRY;
		TAILQ_INIT(&node->u.entry.subs);

		index_count = 0;
		node->u.entry.index = 0;
		tok = gettoken();
		while (tok == TOK_TYPE || tok == TOK_DEFTYPE ||
		    tok == TOK_ENUM || tok == TOK_BITS) {
			char *subtype;
			u_int syntax = parse_type(&tok, NULL, node->name,
			    &subtype);
			if (index_count == SNMP_INDEXES_MAX)
				report("too many table indexes");
			node->u.entry.subtypes[index_count++] = subtype;
			node->u.entry.index |=
			    syntax << (SNMP_INDEX_SHIFT * index_count);
		}
		node->u.entry.index |= index_count;
		if (index_count == 0)
			report("need at least one index");
		if (tok != TOK_STR)
			report("function name expected");

		node->u.entry.func = savetok();

		tok = gettoken();

		while (tok != ')') {
			sub = parse(tok);
			TAILQ_INSERT_TAIL(&node->u.entry.subs, sub, link);
			tok = gettoken();
		}

	} else {
		/* subtree */
		node->type = NODE_TREE;
		TAILQ_INIT(&node->u.tree.subs);

		while (tok != ')') {
			sub = parse(tok);
			TAILQ_INSERT_TAIL(&node->u.tree.subs, sub, link);
			tok = gettoken();
		}
	}
	return (node);
}

/*
 * Parse a top level element. Return the tree if it was a tree, NULL
 * otherwise.
 */
static struct node *
parse_top(enum tok tok)
{
	struct type *t;

	if (tok == '(')
		return (parse(tok));

	if (tok == TOK_TYPEDEF) {
		if (gettoken() != TOK_STR)
			report("type name expected after typedef");

		t = make_type(str);

		tok = gettoken();
		t->is_enum = (tok == TOK_ENUM);
		t->is_bits = (tok == TOK_BITS);

		t->syntax = parse_type(&tok, t, NULL, NULL);
		pushback(tok);

		return (NULL);
	}

	if (tok == TOK_INCLUDE) {
		if (gettoken() != TOK_FILENAME)
			report("filename expected in include directive");

		input_fopen(str, val);
		return (NULL);
	}

	report("'(' or 'typedef' expected");
}

/*
 * Generate the C-code table part for one node.
 */
static void
gen_node(FILE *fp, const struct node *np, struct asn_oid *oid, u_int idx,
    const char *func)
{
	u_int n;
	struct node *sub;
	u_int syntax;

	if (oid->len == ASN_MAXOIDLEN)
		report_node(np, "OID too long");
	oid->subs[oid->len++] = np->id;

	if (np->type == NODE_TREE) {
		TAILQ_FOREACH(sub, &np->u.tree.subs, link)
			gen_node(fp, sub, oid, 0, NULL);
		oid->len--;
		return;
	}
	if (np->type == NODE_ENTRY) {
		TAILQ_FOREACH(sub, &np->u.entry.subs, link)
			gen_node(fp, sub, oid, np->u.entry.index,
			    np->u.entry.func);
		oid->len--;
		return;
	}

	/* leaf or column */
	if ((np->flags & (FL_GET|FL_SET)) == 0) {
		oid->len--;
		return;
	}

	fprintf(fp, "    {{ %u, {", oid->len);
	for (n = 0; n < oid->len; n++)
		fprintf(fp, " %u,", oid->subs[n]);
	fprintf(fp, " }}, \"%s\", ", np->name);

	if (np->type == NODE_COLUMN) {
		syntax = np->u.column.syntax;
		fprintf(fp, "SNMP_NODE_COLUMN, ");
	} else {
		syntax = np->u.leaf.syntax;
		fprintf(fp, "SNMP_NODE_LEAF, ");
	}

	switch (syntax) {

	  case SNMP_SYNTAX_NULL:
		fprintf(fp, "SNMP_SYNTAX_NULL, ");
		break;

	  case SNMP_SYNTAX_INTEGER:
		fprintf(fp, "SNMP_SYNTAX_INTEGER, ");
		break;

	  case SNMP_SYNTAX_OCTETSTRING:
		fprintf(fp, "SNMP_SYNTAX_OCTETSTRING, ");
		break;

	  case SNMP_SYNTAX_IPADDRESS:
		fprintf(fp, "SNMP_SYNTAX_IPADDRESS, ");
		break;

	  case SNMP_SYNTAX_OID:
		fprintf(fp, "SNMP_SYNTAX_OID, ");
		break;

	  case SNMP_SYNTAX_TIMETICKS:
		fprintf(fp, "SNMP_SYNTAX_TIMETICKS, ");
		break;

	  case SNMP_SYNTAX_COUNTER:
		fprintf(fp, "SNMP_SYNTAX_COUNTER, ");
		break;

	  case SNMP_SYNTAX_GAUGE:
		fprintf(fp, "SNMP_SYNTAX_GAUGE, ");
		break;

	  case SNMP_SYNTAX_COUNTER64:
		fprintf(fp, "SNMP_SYNTAX_COUNTER64, ");
		break;

	  case SNMP_SYNTAX_NOSUCHOBJECT:
	  case SNMP_SYNTAX_NOSUCHINSTANCE:
	  case SNMP_SYNTAX_ENDOFMIBVIEW:
		abort();
	}

	if (np->type == NODE_COLUMN)
		fprintf(fp, "%s, ", func);
	else
		fprintf(fp, "%s, ", np->u.leaf.func);

	fprintf(fp, "0");
	if (np->flags & FL_SET)
		fprintf(fp, "|SNMP_NODE_CANSET");
	fprintf(fp, ", %#x, NULL, NULL },\n", idx);
	oid->len--;
	return;
}

/*
 * Generate the header file with the function declarations.
 */
static void
gen_header(FILE *fp, const struct node *np, u_int oidlen, const char *func)
{
	char f[MAXSTR + 4];
	struct node *sub;
	struct func *ptr;

	oidlen++;
	if (np->type == NODE_TREE) {
		TAILQ_FOREACH(sub, &np->u.tree.subs, link)
			gen_header(fp, sub, oidlen, NULL);
		return;
	}
	if (np->type == NODE_ENTRY) {
		TAILQ_FOREACH(sub, &np->u.entry.subs, link)
			gen_header(fp, sub, oidlen, np->u.entry.func);
		return;
	}

 	if((np->flags & (FL_GET|FL_SET)) == 0)
		return;

	if (np->type == NODE_COLUMN) {
		if (func == NULL)
			errx(1, "column without function (%s) - probably "
			    "outside of a table", np->name);
		sprintf(f, "%s", func);
	} else
		sprintf(f, "%s", np->u.leaf.func);

	LIST_FOREACH(ptr, &funcs, link)
		if (strcmp(ptr->name, f) == 0)
			break;

	if (ptr == NULL) {
		ptr = xalloc(sizeof(*ptr));
		ptr->name = savestr(f);
		LIST_INSERT_HEAD(&funcs, ptr, link);

		fprintf(fp, "int	%s(struct snmp_context *, "
		    "struct snmp_value *, u_int, u_int, "
		    "enum snmp_op);\n", f);
	}

	fprintf(fp, "# define LEAF_%s %u\n", np->name, np->id);
}

/*
 * Generate the OID table.
 */
static void
gen_table(FILE *fp, const struct node *node)
{
	struct asn_oid oid;

	fprintf(fp, "#include <sys/types.h>\n");
	fprintf(fp, "#include <stdio.h>\n");
#ifdef HAVE_STDINT_H
	fprintf(fp, "#include <stdint.h>\n");
#endif
	if (localincs) {
		fprintf(fp, "#include \"asn1.h\"\n");
		fprintf(fp, "#include \"snmp.h\"\n");
		fprintf(fp, "#include \"snmpagent.h\"\n");
	} else {
		fprintf(fp, "#include <bsnmp/asn1.h>\n");
		fprintf(fp, "#include <bsnmp/snmp.h>\n");
		fprintf(fp, "#include <bsnmp/snmpagent.h>\n");
	}
	fprintf(fp, "#include \"%stree.h\"\n", file_prefix);
	fprintf(fp, "\n");

	fprintf(fp, "const struct snmp_node %sctree[] = {\n", file_prefix);

	oid.len = PREFIX_LEN;
	memcpy(oid.subs, prefix, sizeof(prefix));
	gen_node(fp, node, &oid, 0, NULL);

	fprintf(fp, "};\n\n");
}

static void
print_syntax(u_int syntax)
{
	u_int i;

	for (i = 0; keywords[i].str != NULL; i++)
		if (keywords[i].tok == TOK_TYPE &&
		    keywords[i].val == syntax) {
			printf(" %s", keywords[i].str);
			return;
	}
	abort();
}

/*
 * Generate a tree definition file
 */
static void
gen_tree(const struct node *np, int level)
{
	const struct node *sp;
	u_int i;

	printf("%*s(%u %s", 2 * level, "", np->id, np->name);

	switch (np->type) {

	  case NODE_LEAF:
		print_syntax(np->u.leaf.syntax);
		if (np->u.leaf.subtype != NULL)
			printf(" | %s", np->u.leaf.subtype);
		printf(" %s%s%s)\n", np->u.leaf.func,
		    (np->flags & FL_GET) ? " GET" : "",
		    (np->flags & FL_SET) ? " SET" : "");
		break;

	  case NODE_TREE:
		if (TAILQ_EMPTY(&np->u.tree.subs)) {
			printf(")\n");
		} else {
			printf("\n");
			TAILQ_FOREACH(sp, &np->u.tree.subs, link)
				gen_tree(sp, level + 1);
			printf("%*s)\n", 2 * level, "");
		}
		break;

	  case NODE_ENTRY:
		printf(" :");

		for (i = 0; i < SNMP_INDEX_COUNT(np->u.entry.index); i++) {
			print_syntax(SNMP_INDEX(np->u.entry.index, i));
			if (np->u.entry.subtypes[i] != NULL)
				printf(" | %s", np->u.entry.subtypes[i]);
		}
		printf(" %s\n", np->u.entry.func);
		TAILQ_FOREACH(sp, &np->u.entry.subs, link)
			gen_tree(sp, level + 1);
		printf("%*s)\n", 2 * level, "");
		break;

	  case NODE_COLUMN:
		print_syntax(np->u.column.syntax);
		if (np->u.column.subtype != NULL)
			printf(" | %s", np->u.column.subtype);
		printf("%s%s)\n", (np->flags & FL_GET) ? " GET" : "",
		    (np->flags & FL_SET) ? " SET" : "");
		break;
	}
}

static int
extract(FILE *fp, const struct node *np, struct asn_oid *oid, const char *obj,
    const struct asn_oid *idx, const char *iname)
{
	struct node *sub;
	u_long n;

	if (oid->len == ASN_MAXOIDLEN)
		report_node(np, "OID too long");
	oid->subs[oid->len++] = np->id;

	if (strcmp(obj, np->name) == 0) {
		if (oid->len + idx->len >= ASN_MAXOIDLEN)
			report_node(np, "OID too long");
		fprintf(fp, "#define OID_%s%s\t%u\n", np->name,
		    iname ? iname : "", np->id);
		fprintf(fp, "#define OIDLEN_%s%s\t%u\n", np->name,
		    iname ? iname : "", oid->len + idx->len);
		fprintf(fp, "#define OIDX_%s%s\t{ %u, {", np->name,
		    iname ? iname : "", oid->len + idx->len);
		for (n = 0; n < oid->len; n++)
			fprintf(fp, " %u,", oid->subs[n]);
		for (n = 0; n < idx->len; n++)
			fprintf(fp, " %u,", idx->subs[n]);
		fprintf(fp, " } }\n");
		return (0);
	}

	if (np->type == NODE_TREE) {
		TAILQ_FOREACH(sub, &np->u.tree.subs, link)
			if (!extract(fp, sub, oid, obj, idx, iname))
				return (0);
	} else if (np->type == NODE_ENTRY) {
		TAILQ_FOREACH(sub, &np->u.entry.subs, link)
			if (!extract(fp, sub, oid, obj, idx, iname))
				return (0);
	}
	oid->len--;
	return (1);
}

static int
gen_extract(FILE *fp, const struct node *root, char *object)
{
	struct asn_oid oid;
	struct asn_oid idx;
	char *s, *e, *end, *iname;
	u_long ul;
	int ret;

	/* look whether the object to extract has an index part */
	idx.len = 0;
	iname = NULL;
	s = strchr(object, '.');
	if (s != NULL) {
		iname = malloc(strlen(s) + 1);
		if (iname == NULL)
			err(1, "cannot allocated index");

		strcpy(iname, s);
		for (e = iname; *e != '\0'; e++)
			if (*e == '.')
				*e = '_';

		*s++ = '\0';
		while (s != NULL) {
			if (*s == '\0')
				errx(1, "bad index syntax");
			if ((e = strchr(s, '.')) != NULL)
				*e++ = '\0';

			errno = 0;
			ul = strtoul(s, &end, 0);
			if (*end != '\0')
				errx(1, "bad index syntax '%s'", end);
			if (errno != 0)
				err(1, "bad index syntax");

			if (idx.len == ASN_MAXOIDLEN)
				errx(1, "index oid too large");
			idx.subs[idx.len++] = ul;

			s = e;
		}
	}

	oid.len = PREFIX_LEN;
	memcpy(oid.subs, prefix, sizeof(prefix));
	ret = extract(fp, root, &oid, object, &idx, iname);
	if (iname != NULL)
		free(iname);

	return (ret);
}


static void
check_sub_order(const struct node *np, const struct node_list *subs)
{
	int first;
	const struct node *sub;
	asn_subid_t maxid = 0;

	/* ensure, that subids are ordered */
	first = 1;
	TAILQ_FOREACH(sub, subs, link) {
		if (!first && sub->id <= maxid)
			report_node(np, "subids not ordered at %s", sub->name);
		maxid = sub->id;
		first = 0;
	}
}

/*
 * Do some sanity checks on the tree definition and do some computations.
 */
static void
check_tree(struct node *np)
{
	struct node *sub;

	if (np->type == NODE_LEAF || np->type == NODE_COLUMN) {
		if ((np->flags & (FL_GET|FL_SET)) != 0)
			tree_size++;
		return;
	}

	if (np->type == NODE_ENTRY) {
		check_sub_order(np, &np->u.entry.subs);

		/* ensure all subnodes are columns */
		TAILQ_FOREACH(sub, &np->u.entry.subs, link) {
			if (sub->type != NODE_COLUMN)
				report_node(np, "entry subnode '%s' is not "
				    "a column", sub->name);
			check_tree(sub);
		}
	} else {
		check_sub_order(np, &np->u.tree.subs);

		TAILQ_FOREACH(sub, &np->u.tree.subs, link)
			check_tree(sub);
	}
}

static void
merge_subs(struct node_list *s1, struct node_list *s2)
{
	struct node *n1, *n2;

	while (!TAILQ_EMPTY(s2)) {
		n2 = TAILQ_FIRST(s2);
		TAILQ_REMOVE(s2, n2, link);

		TAILQ_FOREACH(n1, s1, link)
			if (n1->id >= n2->id)
				break;
		if (n1 == NULL)
			TAILQ_INSERT_TAIL(s1, n2, link);
		else if (n1->id > n2->id)
			TAILQ_INSERT_BEFORE(n1, n2, link);
		else {
			if (n1->type == NODE_TREE && n2->type == NODE_TREE) {
				if (strcmp(n1->name, n2->name) != 0)
					errx(1, "trees to merge must have "
					    "same name '%s' '%s'", n1->name,
					    n2->name);
				merge_subs(&n1->u.tree.subs, &n2->u.tree.subs);
				free(n2);
			} else if (n1->type == NODE_ENTRY &&
			    n2->type == NODE_ENTRY) {
				if (strcmp(n1->name, n2->name) != 0)
					errx(1, "entries to merge must have "
					    "same name '%s' '%s'", n1->name,
					    n2->name);
				if (n1->u.entry.index != n2->u.entry.index)
					errx(1, "entries to merge must have "
					    "same index '%s'", n1->name);
				if (strcmp(n1->u.entry.func,
				    n2->u.entry.func) != 0)
					errx(1, "entries to merge must have "
					    "same op '%s'", n1->name);
				merge_subs(&n1->u.entry.subs,
				    &n2->u.entry.subs);
				free(n2);
			} else
				errx(1, "entities to merge must be both "
				    "trees or both entries: %s, %s",
				    n1->name, n2->name);
		}
	}
}

static void
merge(struct node **root, struct node *t)
{

	if (*root == NULL) {
		*root = t;
		return;
	}
	if (t == NULL)
		return;

	/* both must be trees */
	if ((*root)->type != NODE_TREE)
		errx(1, "root is not a tree");
	if (t->type != NODE_TREE)
		errx(1, "can merge only with tree");
	if ((*root)->id != t->id)
		errx(1, "trees to merge must have same id");

	merge_subs(&(*root)->u.tree.subs, &t->u.tree.subs);
}

static void
unminus(FILE *fp, const char *s)
{

	while (*s != '\0') {
		if (*s == '-')
			fprintf(fp, "_");
		else
			fprintf(fp, "%c", *s);
		s++;
	}
}

/**
 * Generate helper functions for an enum.
 *
 * We always generate a switch statement for the isok function. The compiler
 * optimizes this into range checks if possible.
 *
 * \param fp		file to write to
 * \param t		type
 * \param ccode		generate externally visible non-inline functions
 */
static void
gen_enum_funcs(FILE *fp, const struct type *t, int ccode)
{
	fprintf(fp, "\n");

	if (!ccode)
		fprintf(fp, "static inline ");
	fprintf(fp, "int\n");
	fprintf(fp, "isok_%s(enum %s s)\n", t->name, t->name);
	fprintf(fp, "{\n");
	fprintf(fp, "	switch (s) {\n");

	const struct enums *e;
	TAILQ_FOREACH(e, &t->enums, link) {
		fprintf(fp, "\t  case %s_", t->name);
		unminus(fp, e->name);
		fprintf(fp, ":\n");
	}

	fprintf(fp, "		return (1);\n");
	fprintf(fp, "	}\n");
	fprintf(fp, "	return (0);\n");
	fprintf(fp, "}\n\n");

	if (!ccode)
		fprintf(fp, "static inline ");
	fprintf(fp, "const char *\n");
	fprintf(fp, "tostr_%s(enum %s s)\n", t->name, t->name);
	fprintf(fp, "{\n");
	fprintf(fp, "	static const char *vals[] = { STRING_%s };\n", t->name);
	fprintf(fp, "\n");
	fprintf(fp, "	if (isok_%s(s))\n", t->name);
	fprintf(fp, "		return (vals[(int)s - STROFF_%s]);\n", t->name);
	fprintf(fp, "	return (\"%s???\");\n", t->name);
	fprintf(fp, "}\n\n");

	if (!ccode)
		fprintf(fp, "static inline ");
	fprintf(fp, "int\n");
	fprintf(fp, "fromstr_%s(const char *str, enum %s *s)\n",
	    t->name, t->name);
	fprintf(fp, "{\n");
	fprintf(fp, "	static const char *vals[] = { STRING_%s };\n", t->name);
	fprintf(fp, "\n");
	fprintf(fp, "	for (size_t i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {\n");
	fprintf(fp, "		if (vals[i] != NULL && strcmp(vals[i], str) == 0) {\n");
	fprintf(fp, "			*s = i + STROFF_%s;\n", t->name);
	fprintf(fp, "			return (1);\n");
	fprintf(fp, "		}\n");
	fprintf(fp, "	}\n");
	fprintf(fp, "	return (0);\n");
	fprintf(fp, "}\n");
}

/**
 * Generate a definition for the enum packed into a guard against multiple
 * definitions.
 *
 * \param fp	file to write definition to
 * \param t	type
 * \param dof	generate functions too
 */
static void
gen_enum(FILE *fp, const struct type *t, int dof)
{
	const struct enums *e;
	long min = LONG_MAX;

	fprintf(fp, "\n");
	fprintf(fp, "#ifndef %s_defined__\n", t->name);
	fprintf(fp, "#define %s_defined__\n", t->name);
	fprintf(fp, "/*\n");
	fprintf(fp, " * From %s:%u\n", t->from_fname, t->from_lno);
	fprintf(fp, " */\n");
	fprintf(fp, "enum %s {\n", t->name);
	TAILQ_FOREACH(e, &t->enums, link) {
		fprintf(fp, "\t%s_", t->name);
		unminus(fp, e->name);
		fprintf(fp, " = %ld,\n", e->value);
		if (e->value < min)
			min = e->value;
	}
	fprintf(fp, "};\n");
	fprintf(fp, "#define	STROFF_%s %ld\n", t->name, min);
	fprintf(fp, "#define	STRING_%s \\\n", t->name);
	TAILQ_FOREACH(e, &t->enums, link) {
		fprintf(fp, "\t[%ld] = \"%s_", e->value - min, t->name);
		unminus(fp, e->name);
		fprintf(fp, "\",\\\n");
	}
	fprintf(fp, "\n");
	if (dof) {
		fprintf(fp, "#ifdef SNMPENUM_FUNCS\n");
		fprintf(fp, "\n");
		gen_enum_funcs(fp, t, 0);
		fprintf(fp, "\n");
		fprintf(fp, "#endif\n");
		fprintf(fp, "\n");
	}
	fprintf(fp, "#endif /* %s_defined__ */\n", t->name);
}

/**
 * Generate helper functions for an enum. This generates code for a c file.
 *
 * \param fp		file to write to
 * \param name		enum name
 */
static int
gen_enum_funcs_str(FILE *fp, const char *name)
{
	const struct type *t;

	LIST_FOREACH(t, &types, link)
		if ((t->is_enum || t->is_bits) && strcmp(t->name, name) == 0) {
			gen_enum_funcs(fp, t, 1);
			return (0);
		}

	return (-1);
}

/**
 * Generate helper functions for all enums.
 *
 * \param fp		file to write to
 * \param ccode		generate externally visible non-inline functions
 */
static void
gen_all_enum_funcs(FILE *fp, int ccode)
{
	const struct type *t;

	LIST_FOREACH(t, &types, link)
		if (t->is_enum || t->is_bits)
			gen_enum_funcs(fp, t, ccode);
}

static void
gen_enums(FILE *fp, int dof)
{
	const struct type *t;

	LIST_FOREACH(t, &types, link)
		if (t->is_enum || t->is_bits)
			gen_enum(fp, t, dof);
}

/**
 * Extract a given enum to the specified file and optionally generate static
 * inline helper functions for them.
 *
 * \param fp		file to print on
 * \param name		name of the enum
 * \param gen_funcs	generate the functions too
 *
 * \return 0 if found, -1 otherwise
 */
static int
extract_enum(FILE *fp, const char *name, int gen_funcs)
{
	const struct type *t;

	LIST_FOREACH(t, &types, link)
		if ((t->is_enum || t->is_bits) && strcmp(t->name, name) == 0) {
			gen_enum(fp, t, gen_funcs);
			return (0);
		}
	return (-1);
}

/**
 * Extract all enums to the given file and optionally generate static inline
 * helper functions for them.
 *
 * \param fp		file to print on
 * \param gen_funcs	generate the functions too
 */
static void
extract_all_enums(FILE *fp, int gen_funcs)
{
	const struct type *t;

	LIST_FOREACH(t, &types, link)
		if (t->is_enum || t->is_bits)
			gen_enum(fp, t, gen_funcs);
}

/**
 * Extract enums and optionally generate some helper functions for them.
 *
 * \param argc		number of arguments
 * \param argv		arguments (enum names)
 * \param gen_funcs	which functions to generate
 */
static void
make_enums(int argc, char *argv[], enum gen_funcs gen_funcs)
{
	if (gen_funcs == GEN_FUNCS_C) {
		if (argc == 0)
			gen_all_enum_funcs(stdout, 1);
		else {
			for (int i = 0; i < argc; i++)
				if (gen_enum_funcs_str(stdout, argv[i]))
					errx(1, "enum not found: %s", argv[i]);
		}
	} else {
		if (argc == 0)
			extract_all_enums(stdout, gen_funcs == GEN_FUNCS_H);
		else {
			for (int i = 0; i < argc; i++)
				if (extract_enum(stdout, argv[i],
				    gen_funcs == GEN_FUNCS_H))
					errx(1, "enum not found: %s", argv[i]);
		}
	}
}

/**
 * Produce the operation tables for the daemon or a module.
 *
 * \param root		tree root
 * \param gen_funcs	generate enum funcs
 */
static void
make_table(const struct node *root, int gen_funcs)
{
	FILE *fp;

	char fname[MAXPATHLEN + 1];
	sprintf(fname, "%stree.h", file_prefix);
	if ((fp = fopen(fname, "w")) == NULL)
		err(1, "%s: ", fname);
	gen_header(fp, root, PREFIX_LEN, NULL);

	fprintf(fp, "\n#ifdef SNMPTREE_TYPES\n");
	gen_enums(fp, gen_funcs);
	fprintf(fp, "\n#endif /* SNMPTREE_TYPES */\n\n");

	fprintf(fp, "#define %sCTREE_SIZE %u\n", file_prefix, tree_size);
	fprintf(fp, "extern const struct snmp_node %sctree[];\n", file_prefix);

	fclose(fp);

	sprintf(fname, "%stree.c", file_prefix);
	if ((fp = fopen(fname, "w")) == NULL)
		err(1, "%s: ", fname);
	gen_table(fp, root);
	fclose(fp);
}

int
main(int argc, char *argv[])
{
	enum op op = OP_GEN;
	enum gen_funcs gen_funcs = GEN_FUNCS_NONE;

	char *infile = NULL;

	int opt;
	while ((opt = getopt(argc, argv, "dEeFfhI:i:lp:t")) != EOF)
		switch (opt) {

		  case 'd':
			debug = 1;
			break;

		  case 'E':
			if (op != OP_GEN && op != OP_ENUMS)
				errx(1, "-E conflicts with earlier options");
			op = OP_ENUMS;
			break;

		  case 'e':
			if (op != OP_GEN && op != OP_EXTRACT)
				errx(1, "-e conflicts with earlier options");
			op = OP_EXTRACT;
			break;

		  case 'F':
			if (gen_funcs != GEN_FUNCS_NONE &&
			    gen_funcs != GEN_FUNCS_C)
				errx(1, "-F conflicts with -f");
			gen_funcs = GEN_FUNCS_C;
			break;

		  case 'f':
			if (gen_funcs != GEN_FUNCS_NONE &&
			    gen_funcs != GEN_FUNCS_H)
				errx(1, "-f conflicts with -F");
			gen_funcs = GEN_FUNCS_H;
			break;

		  case 'h':
			fprintf(stderr, "%s", usgtxt);
			exit(0);

		  case 'I':
			path_new(optarg);
			break;

		  case 'i':
			infile = optarg;
			break;

		  case 'l':
			localincs = 1;
			break;

		  case 'p':
			file_prefix = optarg;
			if (strlen(file_prefix) + strlen("tree.c") >
			    MAXPATHLEN)
				errx(1, "prefix too long");
			break;

		  case 't':
			if (op != OP_GEN && op != OP_TREE)
				errx(1, "-t conflicts with earlier options");
			op = OP_TREE;
			break;
		}

	argc -= optind;
	argv += optind;

	/* open input */
	if (infile == NULL) {
		input_new(stdin, NULL, "<stdin>");
	} else {
		FILE *fp;
		if ((fp = fopen(infile, "r")) == NULL)
			err(1, "%s", infile);
		input_new(fp, NULL, infile);
	}

	/* parse and check input */
	struct node *root = parse_top(gettoken());

	int tok;
	while ((tok = gettoken()) != TOK_EOF)
		merge(&root, parse_top(tok));

	if (root)
		check_tree(root);

	/* do what the user has requested */
	switch (op) {

	  case OP_EXTRACT:
		if (argc == 0)
			errx(1, "-e requires arguments");

		for (int i = 0; i < argc; i++)
			if (gen_extract(stdout, root, argv[i]))
				errx(1, "object not found: %s", argv[i]);
		return (0);

	  case OP_ENUMS:
		make_enums(argc, argv, gen_funcs);
		return (0);

	  case OP_TREE:
		if (argc != 0)
			errx(1, "-t allows no arguments");
		gen_tree(root, 0);
		return (0);

	  case OP_GEN:
		if (argc != 0)
			errx(1, "tree generation allows no arguments");
		make_table(root, gen_funcs == GEN_FUNCS_H);
		return (0);
	}
}