/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Read in "high-level" adb script and emit C program.
 * The input may have specifications within {} which
 * we analyze and then emit C code to generate the
 * ultimate adb acript.
 * We are just a filter; no arguments are accepted.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define	streq(s1, s2)	(strcmp(s1, s2) == 0)

#define	LINELEN  	1024	/* max line length expected in input */
#define	STRLEN		128	/* for shorter strings */
#define	NARGS		5	/* number of emitted subroutine arguments */

/*
 * Format specifier strings
 * which are recognized by adbgen when surrounded by {}
 */
#define	FSTR_PTR	"POINTER"
#define	FSTR_LONG_DEC	"LONGDEC"
#define	FSTR_LONG_OCT	"LONGOCT"
#define	FSTR_ULONG_DEC	"ULONGDEC"
#define	FSTR_ULONG_HEX	"ULONGHEX"
#define	FSTR_ULONG_OCT	"ULONGOCT"

/*
 * Types of specifications in {}.
 */
#define	PTR_HEX		0	/* emit hex pointer format char */
#define	LONG_DEC	1	/* emit decimal long format char */
#define	LONG_OCT	2	/* emit octal unsigned long format char */
#define	ULONG_DEC	3	/* emit decimal unsigned long format char */
#define	ULONG_HEX	4	/* emit hexadecimal long format char */
#define	ULONG_OCT	5	/* emit octal unsigned long format char */

#define	FMT_ENTRIES	6	/* number of adbgen format specifier strings */

#define	PRINT   	6	/* print member name with format */
#define	INDIRECT	7	/* fetch member value */
#define	OFFSETOK	8	/* insist that the offset is ok */
#define	SIZEOF		9	/* print sizeof struct */
#define	END		10	/* get offset to end of struct */
#define	OFFSET		11	/* just emit offset */
#define	EXPR		12	/* arbitrary C expression */

/*
 * Special return code from nextchar.
 */
#define	CPP		-2	/* cpp line, restart parsing */

typedef struct adbgen_fmt {
	char *f_str;
	char f_char;
} adbgen_fmt_t;

char struct_name[STRLEN];	/* struct name */
char member[STRLEN];		/* member name */
char format[STRLEN];		/* adb format spec */
char arg[NARGS][STRLEN];	/* arg list for called subroutine */
char *ptr_hex_fmt;		/* adb format character for pointer in hex */
char *long_dec_fmt;		/* adb format character for long in decimal */
char *ulong_dec_fmt;		/* adb format character for ulong in decimal */
char *ulong_hex_fmt;		/* adb format character for ulong in hex */
char *long_oct_fmt;		/* adb format character for long in octal */
char *ulong_oct_fmt;		/* adb format character for ulong in octal */

int line_no = 1;		/* input line number - for error messages */
int specsize;			/* size of {} specification - 1 or 2 parts */
int state;			/* XXX 1 = gathering a printf */
				/* This is a kludge so we emit pending */
				/* printf's when we see a CPP line */

adbgen_fmt_t adbgen_fmt_tbl [FMT_ENTRIES] = {
	{FSTR_PTR},
	{FSTR_LONG_DEC},
	{FSTR_LONG_OCT},
	{FSTR_ULONG_DEC},
	{FSTR_ULONG_HEX},
	{FSTR_ULONG_OCT}
};

void emit_call(char *name, int nargs);
void emit_end(void);
void emit_expr(void);
void emit_indirect(void);
void emit_offset(void);
void emit_offsetok(void);
void emit_print(void);
void emit_printf(char *cp);
void emit_sizeof(void);
void generate(void);
int get_type(void);
int nextchar(char *cp);
void read_spec(void);
char *start_printf(void);

int
main(int argc, char **argv)
{
	char *cp;
	int c;
	int warn_flag = 0;
	int is_lp64 = 0;
	char *usage = "adbgen1 [-w] [-m ilp32|lp64] < <macro file>\n";

	while ((c = getopt(argc, argv, "m:w")) != EOF) {
		switch (c) {
		case 'm':
			if (streq(optarg, "ilp32"))
				is_lp64 = 0;
			else if (streq(optarg, "lp64"))
				is_lp64 = 1;
			else
				fprintf(stderr, usage);
			break;
		case 'w':
			warn_flag++;
			break;
		case '?':
			fprintf(stderr, usage);
			break;
		}
	}
	if (is_lp64) {
		adbgen_fmt_tbl[PTR_HEX].f_char = 'J';
		adbgen_fmt_tbl[LONG_DEC].f_char = 'e';
		adbgen_fmt_tbl[LONG_OCT].f_char = 'g';
		adbgen_fmt_tbl[ULONG_DEC].f_char = 'E';
		adbgen_fmt_tbl[ULONG_HEX].f_char = 'J';
		adbgen_fmt_tbl[ULONG_OCT].f_char = 'G';
	} else {
		adbgen_fmt_tbl[PTR_HEX].f_char = 'X';
		adbgen_fmt_tbl[LONG_DEC].f_char = 'D';
		adbgen_fmt_tbl[LONG_OCT].f_char = 'Q';
		adbgen_fmt_tbl[ULONG_DEC].f_char = 'U';
		adbgen_fmt_tbl[ULONG_HEX].f_char = 'X';
		adbgen_fmt_tbl[ULONG_OCT].f_char = 'O';
	}

	/*
	 * Get structure name.
	 */
	cp = struct_name;
	while ((c = nextchar(NULL)) != '\n') {
		if (c == EOF) {
			fprintf(stderr, "Premature EOF\n");
			exit(1);
		}
		if (c == CPP)
			continue;
		*cp++ = (char)c;
	}
	*cp = '\0';
	/*
	 * Basically, the generated program is just an ongoing printf
	 * with breaks for {} format specifications.
	 */
	printf("\n");
	printf("#include <sys/types.h>\n");
	printf("#include <sys/inttypes.h>\n");
	printf("\n\n");
	printf("int do_fmt(char *acp);\n");
	printf("void format(char *name, size_t size, char *fmt);\n");
	printf("void indirect(off_t offset, size_t size, "
	    "char *base, char *member);\n");
	printf("void offset(off_t off);\n");
	printf("void offsetok(void);\n");
	printf("\n\n");
	printf("main(int argc, char *argv[])\n");
	printf("{\n");
	if (warn_flag) {
		printf("\textern int warnings;\n\n\twarnings = 0;\n");
	}
	cp = start_printf();
	while ((c = nextchar(cp)) != EOF) {
		switch (c) {
		case '"':
			*cp++ = '\\';	/* escape ' in string */
			*cp++ = '"';
			break;
		case '\n':
			*cp++ = '\\';	/* escape newline in string */
			*cp++ = 'n';
			break;
		case '{':
			emit_printf(cp);
			read_spec();
			generate();
			cp = start_printf();
			break;
		case CPP:
			/*
			 * Restart printf after cpp line.
			 */
			cp = start_printf();
			break;
		default:
			*cp++ = c;
			break;
		}
		if (cp - arg[1] >= STRLEN - 10) {
			emit_printf(cp);
			cp = start_printf();
		}
	}
	emit_printf(cp);

	/* terminate program, checking for "error" mode */
	printf("\n\tif (argc > 1 && strcmp(argv[1], \"-e\") == 0) {\n");
	printf("\t\textern int warns;\n\n");
	printf("\t\tif (warns)\n");
	printf("\t\t\treturn (1);\n");
	printf("\t}\n");
	printf("\treturn (0);\n");
	printf("}\n");

	return (0);
}

int
nextchar(char *cp)
{
	int c;
	static int newline = 1;

	c = getchar();
	/*
	 * Lines beginning with '#' and blank lines are passed right through.
	 */
	while (newline) {
		switch (c) {
		case '#':
			if (state)
				emit_printf(cp);
			do {
				putchar(c);
				c = getchar();
				if (c == EOF)
					return (c);
			} while (c != '\n');
			putchar(c);
			line_no++;
			return (CPP);
		case '\n':
			if (state)
				emit_printf(cp);
			putchar(c);
			c = getchar();
			line_no++;
			break;
		default:
			newline = 0;
			break;
		}
	}
	if (c == '\n') {
		newline++;
		line_no++;
	}
	return (c);
}

/*
 * Get started on printf of ongoing adb script.
 */
char *
start_printf(void)
{
	char *cp;

	strcpy(arg[0], "\"%s\"");
	cp = arg[1];
	*cp++ = '"';
	state = 1;			/* XXX */
	return (cp);
}

/*
 * Emit call to printf to print part of ongoing adb script.
 */
void
emit_printf(cp)
	char *cp;
{
	*cp++ = '"';
	*cp = '\0';
	emit_call("printf", 2);
	state = 0;			/* XXX */
}

/*
 * Read {} specification.
 * The first part (up to a comma) is put into "member".
 * The second part, if present, is put into "format".
 */
void
read_spec(void)
{
	char *cp;
	int c;
	int nesting;

	cp = member;
	specsize = 1;
	nesting = 0;
	while ((c = nextchar(NULL)) != '}' || (c == '}' && nesting)) {
		switch (c) {
		case EOF:
			fprintf(stderr, "Unexpected EOF inside {}\n");
			exit(1);
		case '\n':
			fprintf(stderr, "Newline not allowed in {}, line %d\n",
				line_no);
			exit(1);
		case '#':
			fprintf(stderr, "# not allowed in {}, line %d\n",
				line_no);
			exit(1);
		case ',':
			if (specsize == 2) {
				fprintf(stderr, "Excessive commas in {}, ");
				fprintf(stderr, "line %d\n", line_no);
				exit(1);
			}
			specsize = 2;
			*cp = '\0';
			cp = format;
			break;
		case '{':
			/*
			 * Allow up to one set of nested {}'s for adbgen
			 * requests of the form {member, {format string}}
			 */
			if (!nesting) {
				nesting = 1;
				*cp++ = c;
			} else {
				fprintf(stderr, "Too many {'s, line %d\n",
					line_no);
				exit(1);
			}
			break;
		case '}':
			*cp++ = c;
			nesting = 0;
			break;
		default:
			*cp++ = c;
			break;
		}
	}
	*cp = '\0';
	if (cp == member) {
		specsize = 0;
	}
}

/*
 * Decide what type of input specification we have.
 */
int
get_type(void)
{
	int i;

	if (specsize == 1) {
		if (streq(member, "SIZEOF")) {
			return (SIZEOF);
		}
		if (streq(member, "OFFSETOK")) {
			return (OFFSETOK);
		}
		if (streq(member, "END")) {
			return (END);
		}
		for (i = 0; i < FMT_ENTRIES; i++)
			if (streq(member, adbgen_fmt_tbl[i].f_str))
				return (i);
		return (OFFSET);
	}
	if (specsize == 2) {
		if (member[0] == '*') {
			return (INDIRECT);
		}
		if (streq(member, "EXPR")) {
			return (EXPR);
		}
		return (PRINT);
	}
	fprintf(stderr, "Invalid specification, line %d\n", line_no);
	exit(1);
}

/*
 * Generate the appropriate output for an input specification.
 */
void
generate(void)
{
	char *cp;
	int type;

	type = get_type();

	switch (type) {
	case PTR_HEX:
	case LONG_DEC:
	case LONG_OCT:
	case ULONG_DEC:
	case ULONG_HEX:
	case ULONG_OCT:
		cp = start_printf();
		*cp++ = adbgen_fmt_tbl[type].f_char;
		emit_printf(cp);
		break;
	case PRINT:
		emit_print();
		break;
	case OFFSET:
		emit_offset();
		break;
	case INDIRECT:
		emit_indirect();
		break;
	case OFFSETOK:
		emit_offsetok();
		break;
	case SIZEOF:
		emit_sizeof();
		break;
	case EXPR:
		emit_expr();
		break;
	case END:
		emit_end();
		break;
	default:
		fprintf(stderr, "Internal error in generate\n");
		exit(1);
	}
}

/*
 * Emit calls to set the offset and print a member.
 */
void
emit_print(void)
{
	char *cp;
	char fmt_request[STRLEN];
	int i;
	char number[STRLEN];

	emit_offset();
	/*
	 * Emit call to "format" subroutine
	 */
	sprintf(arg[0], "\"%s\"", member);
	sprintf(arg[1], "sizeof ((struct %s *)0)->%s",
		struct_name, member);

	/*
	 * Split the format string into <number><format character string>
	 * This is for format strings that contain format specifier requests
	 * like {POINTER_HEX}, {LONG_DEC}, etc. which need to be substituted
	 * with a format character instead.
	 */
	for (cp = format, i = 0; *cp >= '0' && *cp <= '9' && *cp != '\0';
	    cp++, i++)
		number[i] = *cp;
	number[i] = '\0';

	for (i = 0; i < FMT_ENTRIES; i++) {
		(void) sprintf(fmt_request, "{%s}", adbgen_fmt_tbl[i].f_str);
		if (streq(cp, fmt_request)) {
			sprintf(arg[2], "\"%s%c\"",
				number, adbgen_fmt_tbl[i].f_char);
			break;
		}
	}
	if (i == FMT_ENTRIES)
		sprintf(arg[2], "\"%s\"", format);

	emit_call("format", 3);
}

/*
 * Emit calls to set the offset and print a member.
 */
void
emit_offset(void)
{
	/*
	 * Emit call to "offset" subroutine
	 */
	sprintf(arg[0], "(off_t) &(((struct %s *)0)->%s)",
		struct_name, member);
	emit_call("offset", 1);
}

/*
 * Emit call to indirect routine.
 */
void
emit_indirect(void)
{
	sprintf(arg[0], "(off_t) &(((struct %s *)0)->%s)",
		struct_name, member+1);
	sprintf(arg[1], "sizeof ((struct %s *)0)->%s", struct_name, member+1);
	sprintf(arg[2], "\"%s\"", format);	/* adb register name */
	sprintf(arg[3], "\"%s\"", member);
	emit_call("indirect", 4);
}

/*
 * Emit call to "offsetok" routine.
 */
void
emit_offsetok(void)
{
	emit_call("offsetok", 0);
}

/*
 * Emit call to printf the sizeof the structure.
 */
void
emit_sizeof(void)
{
	sprintf(arg[0], "\"0t%%d\"");
	sprintf(arg[1], "sizeof (struct %s)", struct_name);
	emit_call("printf", 2);
}

/*
 * Emit call to printf an arbitrary C expression.
 */
void
emit_expr(void)
{
	sprintf(arg[0], "\"0t%%d\"");
	sprintf(arg[1], "(%s)", format);
	emit_call("printf", 2);
}

/*
 * Emit call to set offset to end of struct.
 */
void
emit_end(void)
{
	sprintf(arg[0], "sizeof (struct %s)", struct_name);
	emit_call("offset", 1);
}

/*
 * Emit call to subroutine name with nargs arguments from arg array.
 */
void
emit_call(char *name, int nargs)
{
	int i;

	printf("\t%s(", name);		/* name of subroutine */
	for (i = 0; i < nargs; i++) {
		if (i > 0) {
			printf(", ");	/* argument separator */
		}
		printf("%s", arg[i]);	/* argument */
	}
	printf(");\n");			/* end of call */
}