#
# See the file LICENSE for redistribution information.
#
# Copyright (c) 1996, 2012 Oracle and/or its affiliates.  All rights reserved.
#
# $Id$
#

BEGIN {
	if (source_file == "" || header_file == "") {
	    print "Usage: gen_msg.awk requires these variables to be set:";
	    print "\theader_file\t-- the message #include file being created";
	    print "\tsource_file\t-- the message source file being created";
	    exit;
	}
	CFILE=source_file;
	HFILE=header_file;
	maxmsg = 0;
}
/^[	]*PREFIX/ {
	prefix = $2;

	# Start .c files.
	printf("/* Do not edit: automatically built by gen_msg.awk. */\n\n") \
	    > CFILE
	printf("#include \"db_config.h\"\n\n") >> CFILE

	# Start .h file, make the entire file conditional.
	printf("/* Do not edit: automatically built by gen_msg.awk. */\n\n") \
	    > HFILE
	printf("#ifndef\t%s_AUTOMSG_H\n#define\t%s_AUTOMSG_H\n\n", prefix, prefix) \
	    >> HFILE;
	printf("/*\n") >> HFILE;
	printf(" * Message sizes are simply the sum of field sizes (not\n") \
	    >> HFILE;
	printf(" * counting variable size parts, when DBTs are present),\n") \
	    >> HFILE;
	printf(" * and may be different from struct sizes due to padding.\n") \
	    >> HFILE;
	printf(" */\n") >> HFILE;
}
/^[	]*INCLUDE/ {
	for (i = 2; i < NF; i++)
		printf("%s ", $i) >> CFILE;
	printf("%s\n", $i) >> CFILE;
}
/^[	]*BEGIN_MSG/ {
	if (in_begin) {
		print "Invalid format: missing END statement";
		exit;
	}
	in_begin = 1;
	nvars = 0;
	thismsg = $2;
	for (i = 2; i<= NF; i++) {
		if ($i == "alloc")
			alloc = 1;
		else if ($i == "check_length")
			check_length = 1;
		else if ($i == "version")
			version = 1;
	}

	base_name = sprintf("%s_%s", prefix, thismsg);
	typedef_name = sprintf("%s_args", base_name);
	msg_size_name = toupper(sprintf("%s_SIZE", base_name));
	max_name = toupper(sprintf("%s_MAXMSG_SIZE", prefix));
}
/^[	]*ARG/ {
	vars[nvars] = $2;
	types[nvars] = $3;
	if (types[nvars] == "DBT")
		has_dbt = 1;
	nvars++;
}
/^[	]*END/ {
	if (!in_begin) {
		print "Invalid format: missing BEGIN statement";
		exit;
	}
	if (nvars == 0) {
		printf("%s needs at least one field\n", thismsg);
		exit;
	}

	sum = 0;
	for (i = 0; i < nvars; i++)
		sum += type_length(types[i]);
	printf("#define\t%s\t%d\n", msg_size_name, sum) >> HFILE;
	if (sum > maxmsg)
		maxmsg = sum;

	printf("typedef struct _%s {\n", typedef_name) >> HFILE;
	for (i = 0; i < nvars; i++) {
		if (types[i] == "DB_LSN" || types[i] == "DBT")
			printf("\t%s\t\t%s;\n", types[i], vars[i]) >> HFILE;
		else
			printf("\t%s\t%s;\n", types[i], vars[i]) >> HFILE;
	}
	printf("} %s;\n\n", typedef_name) >> HFILE;

	emit_marshal();
	emit_unmarshal();
 
	# Reinitialize variables for next time.
	in_begin = 0;
	alloc = 0;
	check_length = 0;
	version = 0;
	has_dbt = 0;
}
END {
	# End the conditional for the HFILE
	printf("#define\t%s\t%d\n", max_name, maxmsg) >> HFILE;
	printf("#endif\n") >> HFILE;
}

# Length of fixed part of message.  Does not count variable-length data portion
# of DBT.
#
function type_length(type)
{
	if (type == "DB_LSN")
		return (8);
	if (type == "DBT" || type == "u_int32_t" || type == "db_pgno_t")
		return (4);
	if (type == "u_int16_t")
		return (2);
	if (type == "u_int8_t")
		return (1);
	printf("unknown field type: %s", type);
	exit(1);
}

function emit_marshal()
{
	pi = 1;
	if (check_length)
		p[pi++] = "int ";
	else
		p[pi++] = "void ";
	function_name = sprintf("%s_marshal", base_name);
	p[pi++] = function_name;
	p[pi++] = " __P((ENV *, ";
	if (version)
		p[pi++] = "u_int32_t, ";
	p[pi++] = sprintf("%s *, u_int8_t *", typedef_name);
	if (check_length)
		p[pi++] = ", size_t, size_t *";
	p[pi++] = "));";
	proto_format(p, CFILE);

	if (check_length)
		printf("int\n") >> CFILE;
	else
		printf("void\n") >> CFILE;
	printf("%s(env", function_name) >> CFILE;
	if (version)
		printf(", version") >> CFILE;
	printf(", argp, bp") >> CFILE;
	if (check_length)
		printf(", max, lenp") >> CFILE;
	printf(")\n") >> CFILE;

	printf("\tENV *env;\n") >> CFILE;
	if (version)
		printf("\tu_int32_t version;\n") >> CFILE;
	printf("\t%s *argp;\n", typedef_name) >> CFILE;
	printf("\tu_int8_t *bp;\n") >> CFILE;
	if (check_length)
		printf("\tsize_t *lenp, max;\n") >> CFILE;
	printf("{\n") >> CFILE;

	if (version)
		printf("\tint copy_only;\n") >> CFILE;
	if (check_length) {
		printf("\tu_int8_t *start;\n\n") >> CFILE;
		printf("\tif (max < %s", msg_size_name) >> CFILE;
		for (i = 0; i < nvars; i++)
			if (types[i] == "DBT")
				printf("\n\t    + (size_t)argp->%s.size", \
                                    vars[i]) >> CFILE;
		# add in dbt sizes
		printf(")\n") >> CFILE;
		printf("\t\treturn (ENOMEM);\n") >> CFILE;
		printf("\tstart = bp;\n\n") >> CFILE;
	}

	if (version) {
		printf("\tcopy_only = 0;\n") >> CFILE;
		printf("\tif (version < DB_REPVERSION_47)\n") >> CFILE;
		printf("\t\tcopy_only = 1;\n") >> CFILE;
	}
	for (i = 0; i < nvars; i++) {
		if (types[i] == "u_int32_t" || types[i] == "db_pgno_t") {
			if (version) {
				printf("\tif (copy_only) {\n") >> CFILE;
				printf(\
    "\t\tmemcpy(bp, &argp->%s, sizeof(u_int32_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
				printf("\t} else\n\t") >> CFILE;
			}
			printf("\tDB_HTONL_COPYOUT(env, bp, argp->%s);\n", \
                            vars[i]) >> CFILE;
		} else if (types[i] == "u_int16_t") {
			if (version) {
				printf("\tif (copy_only) {\n") >> CFILE;
				printf(\
    "\t\tmemcpy(bp, &argp->%s, sizeof(u_int16_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int16_t);\n") >> CFILE;
				printf("\t} else\n\t") >> CFILE;
			}
			printf("\tDB_HTONS_COPYOUT(env, bp, argp->%s);\n", \
                            vars[i]) >> CFILE;
		} else if (types[i] == "u_int8_t") {
				printf(\
    "\t*bp++ = argp->%s;\n", vars[i]) >> CFILE;
		} else if (types[i] == "DB_LSN") {
			if (version) {
				printf("\tif (copy_only) {\n") >> CFILE;
				printf(\
    "\t\tmemcpy(bp, &argp->%s.file, sizeof(u_int32_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
				printf(\
    "\t\tmemcpy(bp, &argp->%s.offset, sizeof(u_int32_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
				printf("\t} else {\n\t") >> CFILE;
			}
			printf("\tDB_HTONL_COPYOUT(env, bp, argp->%s.file);\n",\
                            vars[i]) >> CFILE;
			if (version)
				printf("\t") >> CFILE;
			printf( \
                            "\tDB_HTONL_COPYOUT(env, bp, argp->%s.offset);\n", \
                            vars[i]) >> CFILE;
			if (version)
				printf("\t}\n") >> CFILE;
		} else if (types[i] == "DBT") {
			if (version) {
				printf("\tif (copy_only) {\n") >> CFILE;
				printf(\
    "\t\tmemcpy(bp, &argp->%s.size, sizeof(u_int32_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
				printf("\t} else\n\t") >> CFILE;
			}
			printf("\tDB_HTONL_COPYOUT(env, bp, argp->%s.size);\n",\
                            vars[i]) >> CFILE;
			printf("\tif (argp->%s.size > 0) {\n", vars[i]) \
                            >> CFILE;
			printf( \
                            "\t\tmemcpy(bp, argp->%s.data, argp->%s.size);\n", \
                            vars[i], vars[i]) >> CFILE;
			printf("\t\tbp += argp->%s.size;\n", vars[i]) >> CFILE;
			printf("\t}\n") >> CFILE;
		} else {
			printf("unknown field type: %s", types[i]);
			exit(1);
		}
	}

	if (check_length) {
		printf("\n\t*lenp = (size_t)(bp - start);\n") >> CFILE;
		printf("\treturn (0);\n") >> CFILE;
	}
	printf("}\n\n") >> CFILE;
}

function emit_unmarshal()
{
	pi = 1;
	p[pi++] = "int ";
	function_name = sprintf("%s_unmarshal", base_name);
	p[pi++] = function_name;
	p[pi++] = " __P((ENV *, ";
	if (version)
		p[pi++] = sprintf("u_int32_t, ");
	if (alloc)
		p[pi++] = sprintf("%s **, u_int8_t *, ", typedef_name);
	else
		p[pi++] = sprintf("%s *, u_int8_t *, ", typedef_name);
	p[pi++] = sprintf("size_t, u_int8_t **));");
	proto_format(p, CFILE);

	printf("int\n") >> CFILE;
	if (alloc)
		arg_name = "argpp";
	else
		arg_name = "argp";
	printf("%s(env, ", function_name) >> CFILE;
	if (version)
		printf("version, ") >> CFILE;
	printf("%s, bp, ", arg_name) >> CFILE;
	printf("max, nextp)\n") >> CFILE;
	printf("\tENV *env;\n") >> CFILE;
	if (version)
		printf("\tu_int32_t version;\n") >> CFILE;
	if (alloc)
		printf("\t%s **argpp;\n", typedef_name) >> CFILE;
	else
		printf("\t%s *argp;\n", typedef_name) >> CFILE;
	printf("\tu_int8_t *bp;\n") >> CFILE;
	printf("\tsize_t max;\n") >> CFILE;
	printf("\tu_int8_t **nextp;\n") >> CFILE;
	printf("{\n") >> CFILE;
	has_locals = 0;
	if (has_dbt) {
		printf("\tsize_t needed;\n") >> CFILE;
		has_locals = 1;
	}
	if (alloc) {
		printf("\t%s *argp;\n", typedef_name) >> CFILE;
		printf("\tint ret;\n") >> CFILE;
		has_locals = 1;
	}
	if (version) {
		printf("\tint copy_only;\n") >> CFILE;
		has_locals = 1;
	}
	if (has_locals)
		printf("\n") >> CFILE;

	# Check that input byte buffer is long enough.
	#
	if (has_dbt) {
		printf("\tneeded = %s;\n", msg_size_name) >> CFILE;
		printf("\tif (max < needed)\n") >> CFILE;
	} else 
		printf("\tif (max < %s)\n", msg_size_name) >> CFILE;
	printf("\t\tgoto too_few;\n") >> CFILE;

	if (alloc) {
		printf( \
              "\tif ((ret = __os_malloc(env, sizeof(*argp), &argp)) != 0)\n") \
		    >> CFILE;
		printf("\t\treturn (ret);\n\n") >> CFILE;
	}
	if (version) {
		printf("\tcopy_only = 0;\n") >> CFILE;
		printf("\tif (version < DB_REPVERSION_47)\n") >> CFILE;
		printf("\t\tcopy_only = 1;\n") >> CFILE;
	}
	
	for (i = 0; i < nvars; i++) {
		if (types[i] == "u_int32_t" || types[i] == "db_pgno_t") {
			if (version) {
				printf("\tif (copy_only) {\n") >> CFILE;
				printf(\
    "\t\tmemcpy(&argp->%s, bp, sizeof(u_int32_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
				printf("\t} else\n\t") >> CFILE;
			}
			printf("\tDB_NTOHL_COPYIN(env, argp->%s, bp);\n", \
                            vars[i]) >> CFILE;
		} else if (types[i] == "u_int16_t") {
			if (version) {
				printf("\tif (copy_only) {\n") >> CFILE;
				printf(\
    "\t\tmemcpy(&argp->%s, bp, sizeof(u_int16_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int16_t);\n") >> CFILE;
				printf("\t} else\n\t") >> CFILE;
			}
			printf("\tDB_NTOHS_COPYIN(env, argp->%s, bp);\n", \
                            vars[i]) >> CFILE;
		} else if (types[i] == "u_int8_t") {
				printf(\
    "\targp->%s = *bp++;\n", vars[i]) >> CFILE;
		} else if (types[i] == "DB_LSN") {
			if (version) {
				printf("\tif (copy_only) {\n") >> CFILE;
				printf(\
    "\t\tmemcpy(&argp->%s.file, bp, sizeof(u_int32_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
				printf(\
    "\t\tmemcpy(&argp->%s.offset, bp, sizeof(u_int32_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
				printf("\t} else {\n\t") >> CFILE;
			}
			printf("\tDB_NTOHL_COPYIN(env, argp->%s.file, bp);\n", \
                            vars[i]) >> CFILE;
			if (version)
				printf("\t") >> CFILE;
			printf( \
                            "\tDB_NTOHL_COPYIN(env, argp->%s.offset, bp);\n", \
                            vars[i]) >> CFILE;
			if (version)
				printf("\t}\n") >> CFILE;
		} else if (types[i] == "DBT") {
			if (version) {
				printf("\tif (copy_only) {\n") >> CFILE;
				printf(\
    "\t\tmemcpy(&argp->%s.size, bp, sizeof(u_int32_t));\n", vars[i]) >> CFILE;
				printf(\
    "\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
				printf("\t} else\n\t") >> CFILE;
			}
			printf("\tDB_NTOHL_COPYIN(env, argp->%s.size, bp);\n", \
                            vars[i]) >> CFILE;
			printf("\tif (argp->%s.size == 0)\n", vars[i]) >> CFILE;
			printf("\t\targp->%s.data = NULL;\n", vars[i]) >> CFILE;
			printf("\telse\n") >> CFILE;
			printf("\t\targp->%s.data = bp;\n", vars[i]) >> CFILE;
			printf("\tneeded += (size_t)argp->%s.size;\n", \
                            vars[i]) >> CFILE;
			printf("\tif (max < needed)\n") >> CFILE;
			printf("\t\tgoto too_few;\n") >> CFILE;
			printf("\tbp += argp->%s.size;\n", vars[i]) >> CFILE;
		} else {
			printf("unknown field type: %s", types[i]);
			exit(1);
		}
	}

	printf("\n\tif (nextp != NULL)\n") >> CFILE;
	printf("\t\t*nextp = bp;\n") >> CFILE;
	if (alloc) {
		printf("\t*argpp = argp;\n") >> CFILE;
	}
	printf("\treturn (0);\n\n") >> CFILE;

	printf("too_few:\n") >> CFILE;
	printf("\t__db_errx(env, DB_STR(\"3675\",\n") >> CFILE;
	printf("\t    \"Not enough input bytes to fill a %s message\"));\n", \
	    base_name) >> CFILE;
	printf("\treturn (EINVAL);\n") >> CFILE;
	printf("}\n\n") >> CFILE;
}	

# proto_format --
#	Pretty-print a function prototype.
function proto_format(p, fp)
{
	printf("/*\n") >> fp;

	s = "";
	for (i = 1; i in p; ++i)
		s = s p[i];

	t = " * PUBLIC: "
	if (length(s) + length(t) < 80)
		printf("%s%s", t, s) >> fp;
	else {
		split(s, p, "__P");
		len = length(t) + length(p[1]);
		printf("%s%s", t, p[1]) >> fp

		n = split(p[2], comma, ",");
		comma[1] = "__P" comma[1];
		for (i = 1; i <= n; i++) {
			if (len + length(comma[i]) > 70) {
				printf("\n * PUBLIC:	") >> fp;
				len = 0;
			}
			printf("%s%s", comma[i], i == n ? "" : ",") >> fp;
			len += length(comma[i]) + 2;
		}
	}
	printf("\n */\n") >> fp;
	delete p;
}
