Tighten up the schedule parsing.

Tighten up the schedule parsing.

Now that it has some arrays in there, more guff turns up.


The assertions generated to test VFS locking never included checks
for vnodes reached through double indirection (i.e. **vpp). This
is worked-around by special-casing the identifier "vpp" (adding one
level of indirection).

The alternative fix mentioned in the PR had required substantial
changes to this script.

In case there are locking violations that had been hidden without
this patch, they may suddenly show up, now ...

This change does not affect code compiled without DEBUG_VFS_LOCKS.

PR:		kern/46652

#!/usr/bin/awk -f

#
# Copyright (c) 1992, 1993
#	The Regents of the University of California.  All rights reserved.
#
# 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.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#	This product includes software developed by the University of
#	California, Berkeley and its contributors.
# 4. Neither the name of the University nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS 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.
#
#	@(#)vnode_if.sh	8.1 (Berkeley) 6/10/93
# $FreeBSD$
#
# Script to produce VFS front-end sugar.
#
# usage: vnode_if.awk <srcfile> [-c | -h]
#	(where <srcfile> is currently /sys/kern/vnode_if.src)
#

function usage()
{
	print "usage: vnode_if.awk <srcfile> [-c|-h]";
	exit 1;
}

function die(msg, what)
{
	printf msg "\n", what > "/dev/stderr";
	exit 1;
}

function t_spc(type)
{
	# Append a space if the type is not a pointer
	return (type ~ /\*$/) ? type : type " ";
}

# These are just for convenience ...
function printc(s) {print s > cfile;}
function printh(s) {print s > hfile;}

function add_debug_code(name, arg, pos)
{
	if (arg == "vpp")
		arg = "*vpp";
	if (lockdata[name, arg, pos]) {
		printh("\tASSERT_VI_UNLOCKED("arg", \""uname"\");");
		# Add assertions for locking
		if (lockdata[name, arg, pos] == "L")
			printh("\tASSERT_VOP_LOCKED("arg", \""uname"\");");
		else if (lockdata[name, arg, pos] == "U")
			printh("\tASSERT_VOP_UNLOCKED("arg", \""uname"\");");
		else if (0) {
			# XXX More checks!
		}
	}
}

function add_debug_pre(name)
{
	if (lockdata[name, "pre"]) {
		printh("#ifdef	DEBUG_VFS_LOCKS");
		printh("\t"lockdata[name, "pre"]"(&a);");
		printh("#endif");
	}
}

function add_debug_post(name)
{
	if (lockdata[name, "post"]) {
		printh("#ifdef	DEBUG_VFS_LOCKS");
		printh("\t"lockdata[name, "post"]"(&a, rc);");
		printh("#endif");
	}
}

function find_arg_with_type (type)
{
	for (jj = 0; jj < numargs; jj++) {
		if (types[jj] == type) {
			return "VOPARG_OFFSETOF(struct " \
			    name "_args,a_" args[jj] ")";
		}
	}

	return "VDESC_NO_OFFSET";
}

BEGIN{

# Process the command line
for (i = 1; i < ARGC; i++) {
	arg = ARGV[i];
	if (arg !~ /^-[ch]+$/ && arg !~ /\.src$/)
		usage();
	if (arg ~ /^-.*c/)
		cfile = "vnode_if.c";
	if (arg ~ /^-.*h/)
		hfile = "vnode_if.h";
	if (arg ~ /\.src$/)
		srcfile = arg;
}
ARGC = 1;

if (!cfile && !hfile)
	exit 0;

if (!srcfile)
	usage();

common_head = \
    "/*\n" \
    " * This file is produced automatically.\n" \
    " * Do not modify anything in here by hand.\n" \
    " *\n" \
    " * Created from $FreeBSD$\n" \
    " */\n" \
    "\n";

if (hfile)
	printh(common_head "extern struct vnodeop_desc vop_default_desc;");

if (cfile) {
	printc(common_head \
	    "#include <sys/param.h>\n" \
	    "#include <sys/systm.h>\n" \
	    "#include <sys/vnode.h>\n" \
	    "\n" \
	    "struct vnodeop_desc vop_default_desc = {\n" \
	    "	1,\t\t\t/* special case, vop_default => 1 */\n" \
	    "	\"default\",\n" \
	    "	0,\n" \
	    "	NULL,\n" \
	    "	VDESC_NO_OFFSET,\n" \
	    "	VDESC_NO_OFFSET,\n" \
	    "	VDESC_NO_OFFSET,\n" \
	    "	VDESC_NO_OFFSET,\n" \
	    "	NULL,\n" \
	    "};\n");
}

while ((getline < srcfile) > 0) {
	if (NF == 0)
		continue;
	if ($1 ~ /^#%/) {
		if (NF != 6  ||  $1 != "#%"  || \
		    $2 !~ /^[a-z]+$/  ||  $3 !~ /^[a-z]+$/  || \
		    $4 !~ /^.$/  ||  $5 !~ /^.$/  ||  $6 !~ /^.$/)
			continue;
		if ($3 == "vpp")
			$3 = "*vpp";
		lockdata["vop_" $2, $3, "Entry"] = $4;
		lockdata["vop_" $2, $3, "OK"]    = $5;
		lockdata["vop_" $2, $3, "Error"] = $6;
		continue;
	}

	if ($1 ~ /^#!/) {
		if (NF != 4 || $1 != "#!")
			continue;
		if ($3 != "pre" && $3 != "post")
			continue;
		lockdata["vop_" $2, $3] = $4;
		continue;
	}
	if ($1 ~ /^#/)
		continue;

	# Get the function name.
	name = $1;
	uname = toupper(name);

	# Start constructing a ktrpoint string
	ctrstr = "\"" uname;
	# Get the function arguments.
	for (numargs = 0; ; ++numargs) {
		if ((getline < srcfile) <= 0) {
			die("Unable to read through the arguments for \"%s\"",
			    name);
		}
		if ($1 ~ /^\};/)
			break;

		# Delete comments, if any.
		gsub (/\/\*.*\*\//, "");

		# Condense whitespace and delete leading/trailing space.
		gsub(/[[:space:]]+/, " ");
		sub(/^ /, "");
		sub(/ $/, "");

		# Pick off direction.
		if ($1 != "INOUT" && $1 != "IN" && $1 != "OUT")
			die("No IN/OUT direction for \"%s\".", $0);
		dirs[numargs] = $1;
		sub(/^[A-Z]* /, "");

		if ((reles[numargs] = $1) == "WILLRELE")
			sub(/^[A-Z]* /, "");
		else
			reles[numargs] = "WONTRELE";

		# kill trailing ;
		if (sub(/;$/, "") < 1)
			die("Missing end-of-line ; in \"%s\".", $0);

		# pick off variable name
		if ((argp = match($0, /[A-Za-z0-9_]+$/)) < 1)
			die("Missing var name \"a_foo\" in \"%s\".", $0);
		args[numargs] = substr($0, argp);
		$0 = substr($0, 1, argp - 1);

		# what is left must be type
		# remove trailing space (if any)
		sub(/ $/, "");
		types[numargs] = $0;

		# We can do a maximum of 6 arguments to CTR*
		if (numargs <= 6) {
			if (numargs == 0)
				ctrstr = ctrstr "(" args[numargs];
			else
				ctrstr = ctrstr ", " args[numargs];
			if (types[numargs] ~ /\*/)
				ctrstr = ctrstr " 0x%lX";
			else
				ctrstr = ctrstr " %ld";
		}
	}
	if (numargs > 6)
		ctrargs = 6;
	else
		ctrargs = numargs;
	ctrstr = "\tCTR" ctrargs "(KTR_VOP, " ctrstr ")\"";
	for (i = 0; i < ctrargs; ++i)
		ctrstr = ctrstr ", " args[i];
	ctrstr = ctrstr ");";

	if (hfile) {
		# Print out the vop_F_args structure.
		printh("struct "name"_args {\n\tstruct vnodeop_desc *a_desc;");
		for (i = 0; i < numargs; ++i)
			printh("\t" t_spc(types[i]) "a_" args[i] ";");
		printh("};");

		# Print out extern declaration.
		printh("extern struct vnodeop_desc " name "_desc;");

		# Print out function.
		printh("static __inline int " uname "(");
		for (i = 0; i < numargs; ++i) {
			printh("\t" t_spc(types[i]) args[i] \
			    (i < numargs - 1 ? "," : ")"));
		}
		printh("{\n\tstruct " name "_args a;");
		printh("\tint rc;");
		printh("\ta.a_desc = VDESC(" name ");");
		for (i = 0; i < numargs; ++i)
			printh("\ta.a_" args[i] " = " args[i] ";");
		for (i = 0; i < numargs; ++i)
			add_debug_code(name, args[i], "Entry");
		add_debug_pre(name);
		printh("\trc = VCALL(" args[0] ", VOFFSET(" name "), &a);");
		printh(ctrstr);
		printh("if (rc == 0) {");
		for (i = 0; i < numargs; ++i)
			add_debug_code(name, args[i], "OK");
		printh("} else {");
		for (i = 0; i < numargs; ++i)
			add_debug_code(name, args[i], "Error");
		printh("}");
		add_debug_post(name);
		printh("\treturn (rc);\n}");
	}

	if (cfile) {
		# Print out the vop_F_vp_offsets structure.  This all depends
		# on naming conventions and nothing else.
		printc("static int " name "_vp_offsets[] = {");
		# as a side effect, figure out the releflags
		releflags = "";
		vpnum = 0;
		for (i = 0; i < numargs; i++) {
			if (types[i] == "struct vnode *") {
				printc("\tVOPARG_OFFSETOF(struct " name \
				    "_args,a_" args[i] "),");
				if (reles[i] == "WILLRELE") {
					releflags = releflags \
					    "|VDESC_VP" vpnum "_WILLRELE";
				}
				vpnum++;
			}
		}

		sub(/^\|/, "", releflags);
		printc("\tVDESC_NO_OFFSET");
		printc("};");

		# Print out the vnodeop_desc structure.
		printc("struct vnodeop_desc " name "_desc = {");
		# offset
		printc("\t0,");
		# printable name
		printc("\t\"" name "\",");
		# flags
		vppwillrele = "";
		for (i = 0; i < numargs; i++) {
			if (types[i] == "struct vnode **" && \
			    reles[i] == "WILLRELE") {
				vppwillrele = "|VDESC_VPP_WILLRELE";
			}
		}

		if (!releflags)
			releflags = "0";
		printc("\t" releflags vppwillrele ",");

		# vp offsets
		printc("\t" name "_vp_offsets,");
		# vpp (if any)
		printc("\t" find_arg_with_type("struct vnode **") ",");
		# cred (if any)
		printc("\t" find_arg_with_type("struct ucred *") ",");
		# thread (if any)
		printc("\t" find_arg_with_type("struct thread *") ",");
		# componentname
		printc("\t" find_arg_with_type("struct componentname *") ",");
		# transport layer information
		printc("\tNULL,\n};\n");
	}
}

if (hfile)
	close(hfile);
if (cfile)
	close(cfile);
close(srcfile);

exit 0;

}

#!/usr/bin/awk -f

#
# Copyright (c) 1992, 1993
#	The Regents of the University of California.  All rights reserved.
#
# 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.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#	This product includes software developed by the University of
#	California, Berkeley and its contributors.
# 4. Neither the name of the University nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS 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.
#
#	@(#)vnode_if.sh	8.1 (Berkeley) 6/10/93
# $FreeBSD$
#
# Script to produce VFS front-end sugar.
#
# usage: vnode_if.awk <srcfile> [-c | -h]
#	(where <srcfile> is currently /sys/kern/vnode_if.src)
#

function usage()
{
	print "usage: vnode_if.awk <srcfile> [-c|-h]";
	exit 1;
}

function die(msg, what)
{
	printf msg "\n", what > "/dev/stderr";
	exit 1;
}

function t_spc(type)
{
	# Append a space if the type is not a pointer
	return (type ~ /\*$/) ? type : type " ";
}

# These are just for convenience ...
function printc(s) {print s > cfile;}
function printh(s) {print s > hfile;}

function add_debug_code(name, arg, pos)
{
	if (lockdata[name, arg, pos]) {
		printh("\tASSERT_VI_UNLOCKED("arg", \""uname"\");");
		# Add assertions for locking
		if (lockdata[name, arg, pos] == "L")
			printh("\tASSERT_VOP_LOCKED("arg", \""uname"\");");
		else if (lockdata[name, arg, pos] == "U")
			printh("\tASSERT_VOP_UNLOCKED("arg", \""uname"\");");
		else if (0) {
			# XXX More checks!
		}
	}
}

function add_debug_pre(name)
{
	if (lockdata[name, "pre"]) {
		printh("#ifdef	DEBUG_VFS_LOCKS");
		printh("\t"lockdata[name, "pre"]"(&a);");
		printh("#endif");
	}
}

function add_debug_post(name)
{
	if (lockdata[name, "post"]) {
		printh("#ifdef	DEBUG_VFS_LOCKS");
		printh("\t"lockdata[name, "post"]"(&a, rc);");
		printh("#endif");
	}
}

function find_arg_with_type (type)
{
	for (jj = 0; jj < numargs; jj++) {
		if (types[jj] == type) {
			return "VOPARG_OFFSETOF(struct " \
			    name "_args,a_" args[jj] ")";
		}
	}

	return "VDESC_NO_OFFSET";
}

BEGIN{

# Process the command line
for (i = 1; i < ARGC; i++) {
	arg = ARGV[i];
	if (arg !~ /^-[ch]+$/ && arg !~ /\.src$/)
		usage();
	if (arg ~ /^-.*c/)
		cfile = "vnode_if.c";
	if (arg ~ /^-.*h/)
		hfile = "vnode_if.h";
	if (arg ~ /\.src$/)
		srcfile = arg;
}
ARGC = 1;

if (!cfile && !hfile)
	exit 0;

if (!srcfile)
	usage();

common_head = \
    "/*\n" \
    " * This file is produced automatically.\n" \
    " * Do not modify anything in here by hand.\n" \
    " *\n" \
    " * Created from $FreeBSD$\n" \
    " */\n" \
    "\n";

if (hfile)
	printh(common_head "extern struct vnodeop_desc vop_default_desc;");

if (cfile) {
	printc(common_head \
	    "#include <sys/param.h>\n" \
	    "#include <sys/systm.h>\n" \
	    "#include <sys/vnode.h>\n" \
	    "\n" \
	    "struct vnodeop_desc vop_default_desc = {\n" \
	    "	1,\t\t\t/* special case, vop_default => 1 */\n" \
	    "	\"default\",\n" \
	    "	0,\n" \
	    "	NULL,\n" \
	    "	VDESC_NO_OFFSET,\n" \
	    "	VDESC_NO_OFFSET,\n" \
	    "	VDESC_NO_OFFSET,\n" \
	    "	VDESC_NO_OFFSET,\n" \
	    "	NULL,\n" \
	    "};\n");
}

while ((getline < srcfile) > 0) {
	if (NF == 0)
		continue;
	if ($1 ~ /^#%/) {
		if (NF != 6  ||  $1 != "#%"  || \
		    $2 !~ /^[a-z]+$/  ||  $3 !~ /^[a-z]+$/  || \
		    $4 !~ /^.$/  ||  $5 !~ /^.$/  ||  $6 !~ /^.$/)
			continue;
		if ($3 == "vpp")
			$3 = "*vpp";
		lockdata["vop_" $2, $3, "Entry"] = $4;
		lockdata["vop_" $2, $3, "OK"]    = $5;
		lockdata["vop_" $2, $3, "Error"] = $6;
		continue;
	}

	if ($1 ~ /^#!/) {
		if (NF != 4 || $1 != "#!")
			continue;
		if ($3 != "pre" && $3 != "post")
			continue;
		lockdata["vop_" $2, $3] = $4;
		continue;
	}
	if ($1 ~ /^#/)
		continue;

	# Get the function name.
	name = $1;
	uname = toupper(name);

	# Start constructing a ktrpoint string
	ctrstr = "\"" uname;
	# Get the function arguments.
	for (numargs = 0; ; ++numargs) {
		if ((getline < srcfile) <= 0) {
			die("Unable to read through the arguments for \"%s\"",
			    name);
		}
		if ($1 ~ /^\};/)
			break;

		# Delete comments, if any.
		gsub (/\/\*.*\*\//, "");

		# Condense whitespace and delete leading/trailing space.
		gsub(/[[:space:]]+/, " ");
		sub(/^ /, "");
		sub(/ $/, "");

		# Pick off direction.
		if ($1 != "INOUT" && $1 != "IN" && $1 != "OUT")
			die("No IN/OUT direction for \"%s\".", $0);
		dirs[numargs] = $1;
		sub(/^[A-Z]* /, "");

		if ((reles[numargs] = $1) == "WILLRELE")
			sub(/^[A-Z]* /, "");
		else
			reles[numargs] = "WONTRELE";

		# kill trailing ;
		if (sub(/;$/, "") < 1)
			die("Missing end-of-line ; in \"%s\".", $0);

		# pick off variable name
		if ((argp = match($0, /[A-Za-z0-9_]+$/)) < 1)
			die("Missing var name \"a_foo\" in \"%s\".", $0);
		args[numargs] = substr($0, argp);
		$0 = substr($0, 1, argp - 1);

		# what is left must be type
		# remove trailing space (if any)
		sub(/ $/, "");
		types[numargs] = $0;

		# We can do a maximum of 6 arguments to CTR*
		if (numargs <= 6) {
			if (numargs == 0)
				ctrstr = ctrstr "(" args[numargs];
			else
				ctrstr = ctrstr ", " args[numargs];
			if (types[numargs] ~ /\*/)
				ctrstr = ctrstr " 0x%lX";
			else
				ctrstr = ctrstr " %ld";
		}
	}
	if (numargs > 6)
		ctrargs = 6;
	else
		ctrargs = numargs;
	ctrstr = "\tCTR" ctrargs "(KTR_VOP, " ctrstr ")\"";
	for (i = 0; i < ctrargs; ++i)
		ctrstr = ctrstr ", " args[i];
	ctrstr = ctrstr ");";

	if (hfile) {
		# Print out the vop_F_args structure.
		printh("struct "name"_args {\n\tstruct vnodeop_desc *a_desc;");
		for (i = 0; i < numargs; ++i)
			printh("\t" t_spc(types[i]) "a_" args[i] ";");
		printh("};");

		# Print out extern declaration.
		printh("extern struct vnodeop_desc " name "_desc;");

		# Print out function.
		printh("static __inline int " uname "(");
		for (i = 0; i < numargs; ++i) {
			printh("\t" t_spc(types[i]) args[i] \
			    (i < numargs - 1 ? "," : ")"));
		}
		printh("{\n\tstruct " name "_args a;");
		printh("\tint rc;");
		printh("\ta.a_desc = VDESC(" name ");");
		for (i = 0; i < numargs; ++i)
			printh("\ta.a_" args[i] " = " args[i] ";");
		for (i = 0; i < numargs; ++i)
			add_debug_code(name, args[i], "Entry");
		add_debug_pre(name);
		printh("\trc = VCALL(" args[0] ", VOFFSET(" name "), &a);");
		printh(ctrstr);
		printh("if (rc == 0) {");
		for (i = 0; i < numargs; ++i)
			add_debug_code(name, args[i], "OK");
		printh("} else {");
		for (i = 0; i < numargs; ++i)
			add_debug_code(name, args[i], "Error");
		printh("}");
		add_debug_post(name);
		printh("\treturn (rc);\n}");
	}

	if (cfile) {
		# Print out the vop_F_vp_offsets structure.  This all depends
		# on naming conventions and nothing else.
		printc("static int " name "_vp_offsets[] = {");
		# as a side effect, figure out the releflags
		releflags = "";
		vpnum = 0;
		for (i = 0; i < numargs; i++) {
			if (types[i] == "struct vnode *") {
				printc("\tVOPARG_OFFSETOF(struct " name \
				    "_args,a_" args[i] "),");
				if (reles[i] == "WILLRELE") {
					releflags = releflags \
					    "|VDESC_VP" vpnum "_WILLRELE";
				}
				vpnum++;
			}
		}

		sub(/^\|/, "", releflags);
		printc("\tVDESC_NO_OFFSET");
		printc("};");

		# Print out the vnodeop_desc structure.
		printc("struct vnodeop_desc " name "_desc = {");
		# offset
		printc("\t0,");
		# printable name
		printc("\t\"" name "\",");
		# flags
		vppwillrele = "";
		for (i = 0; i < numargs; i++) {
			if (types[i] == "struct vnode **" && \
			    reles[i] == "WILLRELE") {
				vppwillrele = "|VDESC_VPP_WILLRELE";
			}
		}

		if (!releflags)
			releflags = "0";
		printc("\t" releflags vppwillrele ",");

		# vp offsets
		printc("\t" name "_vp_offsets,");
		# vpp (if any)
		printc("\t" find_arg_with_type("struct vnode **") ",");
		# cred (if any)
		printc("\t" find_arg_with_type("struct ucred *") ",");
		# thread (if any)
		printc("\t" find_arg_with_type("struct thread *") ",");
		# componentname
		printc("\t" find_arg_with_type("struct componentname *") ",");
		# transport layer information
		printc("\tNULL,\n};\n");
	}
}

if (hfile)
	close(hfile);
if (cfile)
	close(cfile);
close(srcfile);

exit 0;

}


Restore the comment that I removed by accident.

The comment still applies to this block of code.

#!/usr/bin/awk -f

#-
# Copyright (c) 1992, 1993
#        The Regents of the University of California.  All rights reserved.
#
# 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.
# 4. Neither the name of the University nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS 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.
#
# From @(#)vnode_if.sh        8.1 (Berkeley) 6/10/93
# From @(#)makedevops.sh 1.1 1998/06/14 13:53:12 dfr Exp $
# From @(#)makedevops.sh ?.? 1998/10/05
# From src/sys/kern/makedevops.pl,v 1.12 1999/11/22 14:40:04 n_hibma Exp
# From src/sys/kern/makeobjops.pl,v 1.8 2001/11/16 02:02:42 joe Exp
#
# $FreeBSD$

#
#   Script to produce kobj front-end sugar.
#

function usage ()
{
	print "usage: makeobjops.awk <srcfile.m> [-d] [-p] [-l <nr>] [-c|-h]";
	print "where -c   produce only .c files";
	print "      -h   produce only .h files";
	print "      -p   use the path component in the source file for destination dir";
	print "      -l   set line width for output files [80]";
	print "      -d   switch on debugging";
	exit 1;
}

function warn (msg)
{
	print "makeobjops.awk:", msg > "/dev/stderr";
}

function warnsrc (msg)
{
	warn(src ":" lineno ": " msg);
}

function debug (msg)
{
	if (opt_d)
		warn(msg);
}

function die (msg)
{
	warn(msg);
	exit 1;
}

#   These are just for convenience ...
function printc(s) {if (opt_c) print s > ctmpfilename;}
function printh(s) {if (opt_h) print s > htmpfilename;}

#
#   If a line exceeds maxlength, split it into multiple
#   lines at commas.  Subsequent lines are indented by
#   the specified number of spaces.
#
#   In other words:  Lines are split by replacing ", "
#   by ",\n" plus indent spaces.
#

function format_line (line, maxlength, indent)
{
	rline = "";

	while (length(line) > maxlength) {
		#
		#   Find the rightmost ", " so that the part
		#   to the left of it is just within maxlength.
		#   If there is none, give up and leave it as-is.
		#
		if (!match(substr(line, 1, maxlength + 1), /^.*, /))
			break;
		rline = rline substr(line, 1, RLENGTH - 1) "\n";
		line = sprintf("%*s", indent, "") substr(line, RLENGTH + 1);
	}
	return rline line;
}

#
#   Join an array into a string.
#

function join (separator, array, num)
{
	_result = ""
	if (num) {
		while (num > 1)
			_result = separator array[num--] _result;
		_result = array[1] _result;
	}
	return _result;
}

#
#   Execute a system command and report if it failed.
#

function system_check (cmd)
{
	if ((rc = system(cmd)))
		warn(cmd " failed (" rc ")");
}

#
#   Handle "INTERFACE" line.
#

function handle_interface ()
{
	intname = $2;
	sub(/;$/, "", intname);
	if (intname !~ /^[a-z_][a-z0-9_]*$/) {
		debug($0);
		warnsrc("Invalid interface name '" intname "', use [a-z_][a-z0-9_]*");
		error = 1;
		return;
	}
	if (!/;[ 	]*$/)
		warnsrc("Semicolon missing at end of line, no problem");

	debug("Interface " intname);

	printh("#ifndef _" intname "_if_h_");
	printh("#define _" intname "_if_h_\n");
	printc("#include \"" intname "_if.h\"\n");
}

#
# Pass doc comments through to the C file
#
function handle_doc ()
{
	doc = ""
	while (!/\*\//) {
		doc = doc $0 "\n";
		getline < src;
		lineno++;
	}
	doc = doc $0 "\n";
	return doc;
}

#
#   Handle "CODE" and "HEADER" sections.
#   Returns the code as-is.
#

function handle_code ()
{
	code = "\n";
	getline < src;
	indent = $0;
	sub(/[^	 ].*$/, "", indent);	# find the indent used
	while (!/^}/) {
		sub("^" indent, "");	# remove the indent
		code = code $0 "\n";
		getline < src;
		lineno++;;
	}
	return code;
}

#
#   Handle "METHOD" and "STATICMETHOD" sections.
#

function handle_method (static, doc)
{
	#
	#   Get the return type and function name and delete that from
	#   the line. What is left is the possibly first function argument
	#   if it is on the same line.
	#
	if (!intname) {
		warnsrc("No interface name defined");
		error = 1;
		return;
	}
	sub(/^[^ 	]+[ 	]+/, "");
	ret = $0;
	sub(/[ 	]*\{.*$/, "", ret);
	name = ret;
	sub(/^.*[ 	]/, "", name);	# last element is name of method
	sub(/[ 	]+[^ 	]+$/, "", ret);	# return type
	debug("Method: name=" name " return type=" ret);

	sub(/^[^\{]*\{[	 ]*/, "");

	if (!name || !ret) {
		debug($0);
		warnsrc("Invalid method specification");
		error = 1;
		return;
	}

	if (name !~ /^[a-z_][a-z_0-9]*$/) {
		warnsrc("Invalid method name '" name "', use [a-z_][a-z0-9_]*");
		error = 1;
		return;
	}

	if (methods[name]) {
		warnsrc("Duplicate method name");
		error = 1;
		return;
	}
	methods[name] = name;

	line = $0;
	while (line !~ /\}/ && (getline < src) > 0) {
		line = line " " $0;
		lineno++
	}

	default_function = "";
	if (!match(line, /\};?/)) {
		warnsrc("Premature end of file");
		error = 1;
		return;
	}
	extra = substr(line, RSTART + RLENGTH);
	if (extra ~ /[	 ]*DEFAULT[ 	]*[a-zA-Z_][a-zA-Z_0-9]*[ 	]*;/) {
		default_function = extra;
		sub(/.*DEFAULT[	 ]*/, "", default_function);
		sub(/[; 	]+.*$/, "", default_function);
	}
	else if (extra && opt_d) {
		#   Warn about garbage at end of line.
		warnsrc("Ignored '" extra "'");
	}
	sub(/\};?.*$/, "", line);

	#
	#   Create a list of variables without the types prepended.
	#
	sub(/^[	 ]+/, "", line);	# remove leading ...
	sub(/[ 	]+$/, "", line);	# ... and trailing whitespace
	gsub(/[	 ]+/, " ", line);	# remove double spaces

	num_arguments = split(line, arguments, / *; */) - 1;
	delete varnames;		# list of varnames
	num_varnames = 0;
	for (i = 1; i <= num_arguments; i++) {
		if (!arguments[i])
			continue;	# skip argument if argument is empty
		num_ar = split(arguments[i], ar, /[* 	]+/);
		if (num_ar < 2) {	# only 1 word in argument?
			warnsrc("no type for '" arguments[i] "'");
			error = 1;
			return;
		}
		#   Last element is name of variable.
		varnames[++num_varnames] = ar[num_ar];
	}

	argument_list = join(", ", arguments, num_arguments);
	varname_list = join(", ", varnames, num_varnames);

	if (opt_d) {
		warn("Arguments: " argument_list);
		warn("Varnames: " varname_list);
	}

	mname = intname "_" name;	# method name
	umname = toupper(mname);	# uppercase method name

	firstvar = varnames[1];

	if (default_function == "")
		default_function = "kobj_error_method";

	# the method description
	printh("/** @brief Unique descriptor for the " umname "() method */");
	printh("extern struct kobjop_desc " mname "_desc;");
	# the method typedef
	printh("/** @brief A function implementing the " umname "() method */");
	prototype = "typedef " ret " " mname "_t(";
	printh(format_line(prototype argument_list ");",
	    line_width, length(prototype)));

	# Print out the method desc
	printc("struct kobjop_desc " mname "_desc = {");
	printc("\t0, { &" mname "_desc, (kobjop_t)" default_function " }");
	printc("};\n");

	# Print out the method itself
	printh(doc);
	if (0) {		# haven't chosen the format yet
		printh("static __inline " ret " " umname "(" varname_list ")");
		printh("\t" join(";\n\t", arguments, num_arguments) ";");
	}
	else {
		prototype = "static __inline " ret " " umname "(";
		printh(format_line(prototype argument_list ")",
		    line_width, length(prototype)));
	}
	printh("{");
	printh("\tkobjop_t _m;");
	if (!static)
		firstvar = "((kobj_t)" firstvar ")";
	printh("\tKOBJOPLOOKUP(" firstvar "->ops," mname ");");
	retrn =  (ret != "void") ? "return " : "";
	printh("\t" retrn "((" mname "_t *) _m)(" varname_list ");");
	printh("}\n");
}

#
#   Begin of the main program.
#

BEGIN {

line_width = 80;
gerror = 0;

#
#   Process the command line.
#

num_files = 0;

for (i = 1; i < ARGC; i++) {
	if (ARGV[i] ~ /^-/) {
		#
		#   awk doesn't have getopt(), so we have to do it ourselves.
		#   This is a bit clumsy, but it works.
		#
		for (j = 2; j <= length(ARGV[i]); j++) {
			o = substr(ARGV[i], j, 1);
			if	(o == "c")	opt_c = 1;
			else if	(o == "h")	opt_h = 1;
			else if	(o == "p")	opt_p = 1;
			else if	(o == "d")	opt_d = 1;
			else if	(o == "l") {
				if (length(ARGV[i]) > j) {
					opt_l = substr(ARGV[i], j + 1);
					break;
				}
				else {
					if (++i < ARGC)
						opt_l = ARGV[i];
					else
						usage();
				}
			}
			else
				usage();
		}
	}
	else if (ARGV[i] ~ /\.m$/)
		filenames[num_files++] = ARGV[i];
	else
		usage();
}

if (!num_files || !(opt_c || opt_h))
	usage();

if (opt_p)
	debug("Will produce files in original not in current directory");

if (opt_l) {
	if (opt_l !~ /^[0-9]+$/ || opt_l < 1)
		die("Invalid line width '" opt_l "'");
	line_width = opt_l;
	debug("Line width set to " line_width);
}

for (i = 0; i < num_files; i++)
	debug("Filename: " filenames[i]);

for (file_i = 0; file_i < num_files; file_i++) {
	src = filenames[file_i];
	cfilename = hfilename = src;
	sub(/\.m$/, ".c", cfilename);
	sub(/\.m$/, ".h", hfilename);
	if (!opt_p) {
		sub(/^.*\//, "", cfilename);
		sub(/^.*\//, "", hfilename);
	}

	debug("Processing from " src " to " cfilename " / " hfilename);

	ctmpfilename = cfilename ".tmp";
	htmpfilename = hfilename ".tmp";

	common_head = \
	    "/*\n" \
	    " * This file is produced automatically.\n" \
	    " * Do not modify anything in here by hand.\n" \
	    " *\n" \
	    " * Created from source file\n" \
	    " *   " src "\n" \
	    " * with\n" \
	    " *   makeobjops.awk\n" \
	    " *\n" \
	    " * See the source file for legal information\n" \
	    " */\n";

	printc(common_head "\n" \
	    "#include <sys/param.h>\n" \
	    "#include <sys/queue.h>\n" \
	    "#include <sys/kernel.h>\n" \
	    "#include <sys/kobj.h>");

	printh(common_head);

	delete methods;		# clear list of methods
	intname = "";
	lineno = 0;
	error = 0;		# to signal clean up and gerror setting
	lastdoc = "";

	while (!error && (getline < src) > 0) {
		lineno++;

		#
		#   Take special notice of include directives.
		#
		if (/^#[ 	]*include[ 	]+["<][^">]+[">]/) {
			incld = $0;
			sub(/^#[ 	]*include[ 	]+/, "", incld);
			debug("Included file: " incld);
			printc("#include " incld);
		}

		sub(/#.*/, "");		# remove comments
		sub(/^[	 ]+/, "");	# remove leading ...
		sub(/[ 	]+$/, "");	# ... and trailing whitespace

		if (/^$/) {		# skip empty lines
		}
		else if (/^\/\*\*/)
			lastdoc = handle_doc();
		else if (/^INTERFACE[ 	]+[^ 	;]*[ 	]*;?[ 	]*$/) {
			printh(lastdoc);
			lastdoc = "";
			handle_interface();
		} else if (/^CODE[ 	]*{$/)
			printc(handle_code());
		else if (/^HEADER[	 ]*{$/)
			printh(handle_code());
		else if (/^METHOD/) {
			handle_method(0, lastdoc);
			lastdoc = "";
		} else if (/^STATICMETHOD/) {
			handle_method(1, lastdoc);
			lastdoc = "";
		} else {
			debug($0);
			warnsrc("Invalid line encountered");
			error = 1;
		}
	}

	#
	#   Print the final '#endif' in the header file.
	#
	printh("#endif /* _" intname "_if_h_ */");

	close (ctmpfilename);
	close (htmpfilename);

	if (error) {
		warn("Output skipped");
		system_check("rm -f " ctmpfilename " " htmpfilename);
		gerror = 1;
	}
	else {
		if (opt_c)
			system_check("mv -f " ctmpfilename " " cfilename);
		if (opt_h)
			system_check("mv -f " htmpfilename " " hfilename);
	}
}

exit gerror;

}

#!/usr/bin/awk -f

#-
# Copyright (c) 1992, 1993
#        The Regents of the University of California.  All rights reserved.
#
# 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.
# 4. Neither the name of the University nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS 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.
#
# From @(#)vnode_if.sh        8.1 (Berkeley) 6/10/93
# From @(#)makedevops.sh 1.1 1998/06/14 13:53:12 dfr Exp $
# From @(#)makedevops.sh ?.? 1998/10/05
# From src/sys/kern/makedevops.pl,v 1.12 1999/11/22 14:40:04 n_hibma Exp
# From src/sys/kern/makeobjops.pl,v 1.8 2001/11/16 02:02:42 joe Exp
#
# $FreeBSD$

#
#   Script to produce kobj front-end sugar.
#

function usage ()
{
	print "usage: makeobjops.awk <srcfile.m> [-d] [-p] [-l <nr>] [-c|-h]";
	print "where -c   produce only .c files";
	print "      -h   produce only .h files";
	print "      -p   use the path component in the source file for destination dir";
	print "      -l   set line width for output files [80]";
	print "      -d   switch on debugging";
	exit 1;
}

function warn (msg)
{
	print "makeobjops.awk:", msg > "/dev/stderr";
}

function warnsrc (msg)
{
	warn(src ":" lineno ": " msg);
}

function debug (msg)
{
	if (opt_d)
		warn(msg);
}

function die (msg)
{
	warn(msg);
	exit 1;
}

#   These are just for convenience ...
function printc(s) {if (opt_c) print s > ctmpfilename;}
function printh(s) {if (opt_h) print s > htmpfilename;}

#
#   If a line exceeds maxlength, split it into multiple
#   lines at commas.  Subsequent lines are indented by
#   the specified number of spaces.
#
#   In other words:  Lines are split by replacing ", "
#   by ",\n" plus indent spaces.
#

function format_line (line, maxlength, indent)
{
	rline = "";

	while (length(line) > maxlength) {
		#
		#   Find the rightmost ", " so that the part
		#   to the left of it is just within maxlength.
		#   If there is none, give up and leave it as-is.
		#
		if (!match(substr(line, 1, maxlength + 1), /^.*, /))
			break;
		rline = rline substr(line, 1, RLENGTH - 1) "\n";
		line = sprintf("%*s", indent, "") substr(line, RLENGTH + 1);
	}
	return rline line;
}

#
#   Join an array into a string.
#

function join (separator, array, num)
{
	_result = ""
	if (num) {
		while (num > 1)
			_result = separator array[num--] _result;
		_result = array[1] _result;
	}
	return _result;
}

#
#   Execute a system command and report if it failed.
#

function system_check (cmd)
{
	if ((rc = system(cmd)))
		warn(cmd " failed (" rc ")");
}

#
#   Handle "INTERFACE" line.
#

function handle_interface ()
{
	intname = $2;
	sub(/;$/, "", intname);
	if (intname !~ /^[a-z_][a-z0-9_]*$/) {
		debug($0);
		warnsrc("Invalid interface name '" intname "', use [a-z_][a-z0-9_]*");
		error = 1;
		return;
	}
	if (!/;[ 	]*$/)
		warnsrc("Semicolon missing at end of line, no problem");

	debug("Interface " intname);

	printh("#ifndef _" intname "_if_h_");
	printh("#define _" intname "_if_h_\n");
	printc("#include \"" intname "_if.h\"\n");
}

#
# Pass doc comments through to the C file
#
function handle_doc ()
{
	doc = ""
	while (!/\*\//) {
		doc = doc $0 "\n";
		getline < src;
		lineno++;
	}
	doc = doc $0 "\n";
	return doc;
}

#
#   Handle "CODE" and "HEADER" sections.
#   Returns the code as-is.
#

function handle_code ()
{
	code = "\n";
	getline < src;
	indent = $0;
	sub(/[^	 ].*$/, "", indent);	# find the indent used
	while (!/^}/) {
		sub("^" indent, "");	# remove the indent
		code = code $0 "\n";
		getline < src;
		lineno++;;
	}
	return code;
}

#
#   Handle "METHOD" and "STATICMETHOD" sections.
#

function handle_method (static, doc)
{
	#
	#   Get the return type and function name and delete that from
	#   the line. What is left is the possibly first function argument
	#   if it is on the same line.
	#
	if (!intname) {
		warnsrc("No interface name defined");
		error = 1;
		return;
	}
	sub(/^[^ 	]+[ 	]+/, "");
	ret = $0;
	sub(/[ 	]*\{.*$/, "", ret);
	name = ret;
	sub(/^.*[ 	]/, "", name);	# last element is name of method
	sub(/[ 	]+[^ 	]+$/, "", ret);	# return type
	debug("Method: name=" name " return type=" ret);

	sub(/^[^\{]*\{[	 ]*/, "");

	if (!name || !ret) {
		debug($0);
		warnsrc("Invalid method specification");
		error = 1;
		return;
	}

	if (name !~ /^[a-z_][a-z_0-9]*$/) {
		warnsrc("Invalid method name '" name "', use [a-z_][a-z0-9_]*");
		error = 1;
		return;
	}

	if (methods[name]) {
		warnsrc("Duplicate method name");
		error = 1;
		return;
	}
	methods[name] = name;

	line = $0;
	while (line !~ /\}/ && (getline < src) > 0) {
		line = line " " $0;
		lineno++
	}

	default_function = "";
	if (!match(line, /\};?/)) {
		warnsrc("Premature end of file");
		error = 1;
		return;
	}
	extra = substr(line, RSTART + RLENGTH);
	if (extra ~ /[	 ]*DEFAULT[ 	]*[a-zA-Z_][a-zA-Z_0-9]*[ 	]*;/) {
		default_function = extra;
		sub(/.*DEFAULT[	 ]*/, "", default_function);
		sub(/[; 	]+.*$/, "", default_function);
	}
	else if (extra && opt_d) {
		#   Warn about garbage at end of line.
		warnsrc("Ignored '" extra "'");
	}
	sub(/\};?.*$/, "", line);

	#
	#   Create a list of variables without the types prepended.
	#
	sub(/^[	 ]+/, "", line);	# remove leading ...
	sub(/[ 	]+$/, "", line);	# ... and trailing whitespace
	gsub(/[	 ]+/, " ", line);	# remove double spaces

	num_arguments = split(line, arguments, / *; */) - 1;
	delete varnames;		# list of varnames
	num_varnames = 0;
	for (i = 1; i <= num_arguments; i++) {
		if (!arguments[i])
			continue;	# skip argument if argument is empty
		num_ar = split(arguments[i], ar, /[* 	]+/);
		if (num_ar < 2) {	# only 1 word in argument?
			warnsrc("no type for '" arguments[i] "'");
			error = 1;
			return;
		}
		#   Last element is name of variable.
		varnames[++num_varnames] = ar[num_ar];
	}

	argument_list = join(", ", arguments, num_arguments);
	varname_list = join(", ", varnames, num_varnames);

	if (opt_d) {
		warn("Arguments: " argument_list);
		warn("Varnames: " varname_list);
	}

	mname = intname "_" name;	# method name
	umname = toupper(mname);	# uppercase method name

	firstvar = varnames[1];

	if (default_function == "")
		default_function = "kobj_error_method";

	# the method description
	printh("/** @brief Unique descriptor for the " umname "() method */");
	printh("extern struct kobjop_desc " mname "_desc;");
	# the method typedef
	printh("/** @brief A function implementing the " umname "() method */");
	prototype = "typedef " ret " " mname "_t(";
	printh(format_line(prototype argument_list ");",
	    line_width, length(prototype)));

	printc("struct kobjop_desc " mname "_desc = {");
	printc("\t0, { &" mname "_desc, (kobjop_t)" default_function " }");
	printc("};\n");

	# Print out the method itself
	printh(doc);
	if (0) {		# haven't chosen the format yet
		printh("static __inline " ret " " umname "(" varname_list ")");
		printh("\t" join(";\n\t", arguments, num_arguments) ";");
	}
	else {
		prototype = "static __inline " ret " " umname "(";
		printh(format_line(prototype argument_list ")",
		    line_width, length(prototype)));
	}
	printh("{");
	printh("\tkobjop_t _m;");
	if (!static)
		firstvar = "((kobj_t)" firstvar ")";
	printh("\tKOBJOPLOOKUP(" firstvar "->ops," mname ");");
	retrn =  (ret != "void") ? "return " : "";
	printh("\t" retrn "((" mname "_t *) _m)(" varname_list ");");
	printh("}\n");
}

#
#   Begin of the main program.
#

BEGIN {

line_width = 80;
gerror = 0;

#
#   Process the command line.
#

num_files = 0;

for (i = 1; i < ARGC; i++) {
	if (ARGV[i] ~ /^-/) {
		#
		#   awk doesn't have getopt(), so we have to do it ourselves.
		#   This is a bit clumsy, but it works.
		#
		for (j = 2; j <= length(ARGV[i]); j++) {
			o = substr(ARGV[i], j, 1);
			if	(o == "c")	opt_c = 1;
			else if	(o == "h")	opt_h = 1;
			else if	(o == "p")	opt_p = 1;
			else if	(o == "d")	opt_d = 1;
			else if	(o == "l") {
				if (length(ARGV[i]) > j) {
					opt_l = substr(ARGV[i], j + 1);
					break;
				}
				else {
					if (++i < ARGC)
						opt_l = ARGV[i];
					else
						usage();
				}
			}
			else
				usage();
		}
	}
	else if (ARGV[i] ~ /\.m$/)
		filenames[num_files++] = ARGV[i];
	else
		usage();
}

if (!num_files || !(opt_c || opt_h))
	usage();

if (opt_p)
	debug("Will produce files in original not in current directory");

if (opt_l) {
	if (opt_l !~ /^[0-9]+$/ || opt_l < 1)
		die("Invalid line width '" opt_l "'");
	line_width = opt_l;
	debug("Line width set to " line_width);
}

for (i = 0; i < num_files; i++)
	debug("Filename: " filenames[i]);

for (file_i = 0; file_i < num_files; file_i++) {
	src = filenames[file_i];
	cfilename = hfilename = src;
	sub(/\.m$/, ".c", cfilename);
	sub(/\.m$/, ".h", hfilename);
	if (!opt_p) {
		sub(/^.*\//, "", cfilename);
		sub(/^.*\//, "", hfilename);
	}

	debug("Processing from " src " to " cfilename " / " hfilename);

	ctmpfilename = cfilename ".tmp";
	htmpfilename = hfilename ".tmp";

	common_head = \
	    "/*\n" \
	    " * This file is produced automatically.\n" \
	    " * Do not modify anything in here by hand.\n" \
	    " *\n" \
	    " * Created from source file\n" \
	    " *   " src "\n" \
	    " * with\n" \
	    " *   makeobjops.awk\n" \
	    " *\n" \
	    " * See the source file for legal information\n" \
	    " */\n";

	printc(common_head "\n" \
	    "#include <sys/param.h>\n" \
	    "#include <sys/queue.h>\n" \
	    "#include <sys/kernel.h>\n" \
	    "#include <sys/kobj.h>");

	printh(common_head);

	delete methods;		# clear list of methods
	intname = "";
	lineno = 0;
	error = 0;		# to signal clean up and gerror setting
	lastdoc = "";

	while (!error && (getline < src) > 0) {
		lineno++;

		#
		#   Take special notice of include directives.
		#
		if (/^#[ 	]*include[ 	]+["<][^">]+[">]/) {
			incld = $0;
			sub(/^#[ 	]*include[ 	]+/, "", incld);
			debug("Included file: " incld);
			printc("#include " incld);
		}

		sub(/#.*/, "");		# remove comments
		sub(/^[	 ]+/, "");	# remove leading ...
		sub(/[ 	]+$/, "");	# ... and trailing whitespace

		if (/^$/) {		# skip empty lines
		}
		else if (/^\/\*\*/)
			lastdoc = handle_doc();
		else if (/^INTERFACE[ 	]+[^ 	;]*[ 	]*;?[ 	]*$/) {
			printh(lastdoc);
			lastdoc = "";
			handle_interface();
		} else if (/^CODE[ 	]*{$/)
			printc(handle_code());
		else if (/^HEADER[	 ]*{$/)
			printh(handle_code());
		else if (/^METHOD/) {
			handle_method(0, lastdoc);
			lastdoc = "";
		} else if (/^STATICMETHOD/) {
			handle_method(1, lastdoc);
			lastdoc = "";
		} else {
			debug($0);
			warnsrc("Invalid line encountered");
			error = 1;
		}
	}

	#
	#   Print the final '#endif' in the header file.
	#
	printh("#endif /* _" intname "_if_h_ */");

	close (ctmpfilename);
	close (htmpfilename);

	if (error) {
		warn("Output skipped");
		system_check("rm -f " ctmpfilename " " htmpfilename);
		gerror = 1;
	}
	else {
		if (opt_c)
			system_check("mv -f " ctmpfilename " " cfilename);
		if (opt_h)
			system_check("mv -f " htmpfilename " " hfilename);
	}
}

exit gerror;

}


OOO330

#*************************************************************************
#
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# Copyright 2000, 2010 Oracle and/or its affiliates.
#
# OpenOffice.org - a multi-platform office productivity suite
#
# This file is part of OpenOffice.org.
#
# OpenOffice.org is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# only, as published by the Free Software Foundation.
#
# OpenOffice.org is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License version 3 for more details
# (a copy is included in the LICENSE file that accompanied this code).
#
# You should have received a copy of the GNU Lesser General Public License
# version 3 along with OpenOffice.org.  If not, see
# <http://www.openoffice.org/license.html>
# for a copy of the LGPLv3 License.
#
#*************************************************************************

# Add certain symbol patterns to the first global section.
#
# The below code fails with 'perverted' mapfiles (using a strange line layout,
# or containing version UDK_3_0_0 without a global section, ...).

BEGIN { state = 0 }
END {
    if (state == 0) {
        print "# Weak RTTI symbols for C++ exceptions:"
        print "UDK_3_0_0 {"
        print "global:"
        print "_ZTI*; _ZTS*; # weak RTTI symbols for C++ exceptions"
        if (ENVIRON["USE_SYSTEM_STL"] != "YES")
            print "_ZN4_STL7num_put*; # for STLport"
        print "};"
    }
}
state == 2 {
    print "_ZTI*; _ZTS*; # weak RTTI symbols for C++ exceptions"
    if (ENVIRON["USE_SYSTEM_STL"] != "YES")
        print "_ZN4_STL7num_put*; # for STLport"
    state = 3
}
# #i66636# - ???
/^[\t ]*UDK_3_0_0[\t ]*\{/ { state = 1 }
/^[\t ]*global[\t ]*:/ && state == 1 { state = 2 }
{ print }

#*************************************************************************
#
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# Copyright 2000, 2010 Oracle and/or its affiliates.
#
# OpenOffice.org - a multi-platform office productivity suite
#
# This file is part of OpenOffice.org.
#
# OpenOffice.org is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# only, as published by the Free Software Foundation.
#
# OpenOffice.org is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License version 3 for more details
# (a copy is included in the LICENSE file that accompanied this code).
#
# You should have received a copy of the GNU Lesser General Public License
# version 3 along with OpenOffice.org.  If not, see
# <http://www.openoffice.org/license.html>
# for a copy of the LGPLv3 License.
#
#*************************************************************************

# Add certain symbol patterns to the first global section.

BEGIN { state = 0 }
/\{/ && state == 1 { exit 1 } #TODO: print error explanation to stderr?
/^[\t ]*UDK_3_0_0[\t ]*\{/ && state == 0 { state = 1 }
/^[\t ]*global[\t ]*:/ && state == 1 { state = 2 }
{ print }
state == 2 {
    print "_ZTI*; _ZTS*; # weak RTTI symbols for C++ exceptions"
    if (ENVIRON["USE_SYSTEM_STL"] != "YES")
        print "_ZN4_STL7num_put*; # for STLport"
    state = 3
}
END { if (state != 3) exit 1 } #TODO: print error explanation to stderr?


fixed eparams for new version bug - was referencing old dataset

# update-e-params-subject-discrim.awk
#
# params:
#
#   -v baseURI
#   -v sourceID
#   -v dataset_identifier
#   -v datasetVersion
#   -v layerID
#   -v subjectDiscriminator

{
   if( $1 == "@prefix" && $2 == ":" ) {
      # Fix up default namespace.
      printf("@prefix :           <%s/source/%s/dataset/%s/version/%s/params/enhancement/%s/> .\n",baseURI,sourceID,dataset_identifier,datasetVersion,layerID);

   }else if( $0 == ":dataset a void:Dataset;" ) {
      # Change subject to name of layer dataset itself.
      printf("<%s/source/%s/dataset/%s/version/%s/conversion/enhancement/%s>\n",baseURI,sourceID,dataset_identifier,datasetVersion,layerID);

   }else if( /^</ && /source\// && /dataset\// && /version\// && /conversion\// && /enhancement\/[^\/]*>/ ) {
      # <http://logd.tw.rpi.edu/source/lebot/dataset/golfers/version/2012-Mar-11/conversion/enhancement/1>
      # becomes
      # <http://logd.tw.rpi.edu/source/lebot/dataset/golfers/version/2012-Mar-15/conversion/enhancement/1>
      sub(/version\/[^\/]*/,"version/"datasetVersion);
      print $0
   }else if( $0~"conversion:subject_discriminator *\"" ) {
      printf("      conversion:subject_discriminator \"%s\";\n",subjectDiscriminator)

   }else if( $0~"conversion:dataset_version *\"" ) {                          # TODO: DEPRECATED (but converter not recognizing it)
      printf("   conversion:dataset_version    \"%s\";\n",datasetVersion)

   }else if( $0~"conversion:version_identifier *\"" ) {
      printf("   conversion:version_identifier \"%s\";\n",datasetVersion)

   }else if( $0~"conversion:dataset_identifier *\"" && length(dataset_identifier) > 0) {
      printf("   conversion:dataset_identifier \"%s\";\n",dataset_identifier)

   }else {
      print
   }
}

# update-e-params-subject-discrim.awk
#
# params:
#
#   -v baseURI
#   -v sourceID
#   -v dataset_identifier
#   -v datasetVersion
#   -v layerID
#   -v subjectDiscriminator

{
   if( $1 == "@prefix" && $2 == ":" ) {
      # Fix up default namespace.
      printf("@prefix :           <%s/source/%s/dataset/%s/version/%s/params/enhancement/%s/> .\n",baseURI,sourceID,dataset_identifier,datasetVersion,layerID);

   }else if( $0 == ":dataset a void:Dataset;" ) {
      # Change subject to name of layer dataset itself.
      printf("<%s/source/%s/dataset/%s/version/%s/conversion/enhancement/%s>\n",baseURI,sourceID,dataset_identifier,datasetVersion,layerID);

   }else if( $0~"conversion:subject_discriminator *\"" ) {
      printf("      conversion:subject_discriminator \"%s\";\n",subjectDiscriminator)

   }else if( $0~"conversion:dataset_version *\"" ) {                          # TODO: DEPRECATED (but converter not recognizing it)
      printf("   conversion:dataset_version    \"%s\";\n",datasetVersion)

   }else if( $0~"conversion:version_identifier *\"" ) {
      printf("   conversion:version_identifier \"%s\";\n",datasetVersion)

   }else if( $0~"conversion:dataset_identifier *\"" && length(dataset_identifier) > 0) {
      printf("   conversion:dataset_identifier \"%s\";\n",dataset_identifier)

   }else {
      print
   }
}


tests: fix build with awk that does not support switch-case

The switch-case statement is a gawk-specific feature which is not
necessarily available, let's use traditional if-else statements instead.

* tests/uid.awk: Rewrite without switch-case statements.

BEGIN {
  r_uint = "(0|[1-9][0-9]*)"
  regexp = "^getuid" suffix "\\(\\)[[:space:]]+= " r_uint "$"
  expected = "getuid"
  fail = 0
}

regexp == "" {
  fail = 1
  next
}

{
  if (match($0, regexp, a)) {
    if (expected == "getuid") {
      uid = a[1]
      expected = "setuid"
      regexp = "^setuid" suffix "\\(" uid "\\)[[:space:]]+= 0$"
    } else if (expected == "setuid") {
      expected = "getresuid"
      regexp = "^getresuid" suffix "\\(\\[" uid "\\], \\[" uid "\\], \\[" uid "\\]\\)[[:space:]]+= 0$"
    } else if (expected == "getresuid") {
      expected = "setreuid"
      regexp = "^setreuid" suffix "\\(-1, -1\\)[[:space:]]+= 0$"
    } else if (expected == "setreuid") {
      expected = "setresuid"
      regexp = "^setresuid" suffix "\\(-1, " uid ", -1\\)[[:space:]]+= 0$"
    } else if (expected == "setresuid") {
      expected = "chown"
      regexp = "^chown" suffix "\\(\".\", -1, -1\\)[[:space:]]+= 0$"
    } else if (expected == "chown") {
      expected = "1st getgroups"
      regexp = "^getgroups" suffix "\\(0, NULL\\)[[:space:]]+= " r_uint "$"
    } else if (expected == "1st getgroups") {
      ngroups = a[1]
      if (ngroups == "0")
        list=""
      else if (ngroups == "1")
        list=r_uint
      else
        list=r_uint "(, " r_uint "){" (ngroups - 1) "}"
      expected = "2nd getgroups"
      regexp = "^getgroups" suffix "\\(" ngroups ", \\[" list "\\]\\)[[:space:]]+= " ngroups "$"
    } else if (expected == "2nd getgroups") {
      expected = "the last line"
      regexp = "^\\+\\+\\+ exited with 0 \\+\\+\\+$"
    } else if (expected == "the last line") {
      expected = "nothing"
      regexp = ""
    }
  }
}

END {
  if (fail) {
    print "Unexpected output after exit"
    exit 1
  }
  if (regexp == "")
    exit 0
  print "error: " expected " doesn't match"
  exit 1
}

BEGIN {
  r_uint = "(0|[1-9][0-9]*)"
  regexp = "^getuid" suffix "\\(\\)[[:space:]]+= " r_uint "$"
  expected = "getuid"
  fail = 0
}

regexp == "" {
  fail = 1
  next
}

{
  if (match($0, regexp, a)) {
    switch (expected) {
      case "getuid":
        uid = a[1]
        expected = "setuid"
        regexp = "^setuid" suffix "\\(" uid "\\)[[:space:]]+= 0$"
        next
      case "setuid":
        expected = "getresuid"
        regexp = "^getresuid" suffix "\\(\\[" uid "\\], \\[" uid "\\], \\[" uid "\\]\\)[[:space:]]+= 0$"
        next
      case "getresuid":
        expected = "setreuid"
        regexp = "^setreuid" suffix "\\(-1, -1\\)[[:space:]]+= 0$"
        next
      case "setreuid":
        expected = "setresuid"
        regexp = "^setresuid" suffix "\\(-1, " uid ", -1\\)[[:space:]]+= 0$"
        next
      case "setresuid":
        expected = "chown"
        regexp = "^chown" suffix "\\(\".\", -1, -1\\)[[:space:]]+= 0$"
        next
      case "chown":
        expected = "1st getgroups"
        regexp = "^getgroups" suffix "\\(0, NULL\\)[[:space:]]+= " r_uint "$"
        next
      case "1st getgroups":
        ngroups = a[1]
        switch (ngroups) {
          case "0": list=""; break
          case "1": list=r_uint; break
          default: list=r_uint "(, " r_uint "){" (ngroups - 1) "}"
        }
        expected = "2nd getgroups"
        regexp = "^getgroups" suffix "\\(" ngroups ", \\[" list "\\]\\)[[:space:]]+= " ngroups "$"
        next
      case "2nd getgroups":
        expected = "the last line"
        regexp = "^\\+\\+\\+ exited with 0 \\+\\+\\+$"
        next
      case "the last line":
        expected = "nothing"
        regexp = ""
        next
    }
  }
  next
}

END {
  if (fail) {
    print "Unexpected output after exit"
    exit 1
  }
  if (regexp == "")
    exit 0
  print "error: " expected " doesn't match"
  exit 1
}


Commons: add ansi(code, text)

Commons: add ansi(code, text)

####################################################################
# Commons.awk                                                      #
#                                                                  #
# Commonly used functions for string and array operations, logging.#
####################################################################

# Initialize `UrlEncoding`.
# See: <https://en.wikipedia.org/wiki/Percent-encoding>
function initUrlEncoding() {
    UrlEncoding["\n"] = "%0A"
    UrlEncoding[" "]  = "%20"
    UrlEncoding["!"]  = "%21"
    UrlEncoding["\""] = "%22"
    UrlEncoding["#"]  = "%23"
    UrlEncoding["$"]  = "%24"
    UrlEncoding["%"]  = "%25"
    UrlEncoding["&"]  = "%26"
    UrlEncoding["'"]  = "%27"
    UrlEncoding["("]  = "%28"
    UrlEncoding[")"]  = "%29"
    UrlEncoding["*"]  = "%2A"
    UrlEncoding["+"]  = "%2B"
    UrlEncoding[","]  = "%2C"
    UrlEncoding["-"]  = "%2D"
    UrlEncoding["."]  = "%2E"
    UrlEncoding["/"]  = "%2F"
    UrlEncoding[":"]  = "%3A"
    UrlEncoding[";"]  = "%3B"
    UrlEncoding["<"]  = "%3C"
    UrlEncoding["="]  = "%3D"
    UrlEncoding[">"]  = "%3E"
    UrlEncoding["?"]  = "%3F"
    UrlEncoding["@"]  = "%40"
    UrlEncoding["["]  = "%5B"
    UrlEncoding["\\"] = "%5C"
    UrlEncoding["]"]  = "%5D"
    UrlEncoding["^"]  = "%5E"
    UrlEncoding["_"]  = "%5F"
    UrlEncoding["`"]  = "%60"
    UrlEncoding["{"]  = "%7B"
    UrlEncoding["|"]  = "%7C"
    UrlEncoding["}"]  = "%7D"
    UrlEncoding["~"]  = "%7E"
}

# Return the real character represented by an escape sequence.
# Example: escapeChar("n") returns "\n".
# See: <https://en.wikipedia.org/wiki/Escape_character>
#      <https://en.wikipedia.org/wiki/Escape_sequences_in_C>
function escapeChar(char) {
    switch (char) {
    case "b":
        return "\b" # Backspace
    case "f":
        return "\f" # Formfeed
    case "n":
        return "\n" # Newline (Line Feed)
    case "r":
        return "\r" # Carriage Return
    case "t":
        return "\t" # Horizontal Tab
    case "v":
        return "\v" # Vertical Tab
    default:
        return char
    }
}

# Convert a literal-formatted string into its original string.
function literal(string,
                 ####
                 c, escaping, i, s) {
    if (string !~ /^".*"$/)
        return string

    split(string, s, "")
    string = ""
    escaping = 0
    for (i = 2; i < length(s); i++) {
        c = s[i]
        if (escaping) {
            string = string escapeChar(c)
            escaping = 0 # escape ends
        } else {
            if (c == "\\")
                escaping = 1 # escape begins
            else
                string = string c
        }
    }
    return string
}

# Return the escaped string.
function escape(string) {
    gsub(/"/, "\\\"", string)
    gsub(/\\/, "\\\\", string)
    return string
}

# Return the escaped, quoted string.
function parameterize(string, quotationMark) {
    if (!quotationMark)
        quotationMark = "'"

    if (quotationMark == "'") {
        gsub(/'/, "'\\''", string)
        return "'" string "'"
    } else {
        return "\"" escape(string) "\""
    }
}

# Return the URL-encoded string.
function quote(string,    i, r, s) {
    r = ""
    split(string, s, "")
    for (i = 1; i <= length(s); i++)
        r = r (s[i] in UrlEncoding ? UrlEncoding[s[i]] : s[i])
    return r
}

# Replicate a string.
function replicate(string, len,
                   ####
                   i, temp) {
    temp = ""
    for (i = 0; i < len; i++)
        temp = temp string
    return temp
}

# Squeeze a source line of AWK code.
function squeeze(line) {
    # Remove preceding spaces
    gsub(/^[[:space:]]+/, "", line)
    # Remove in-line comment
    gsub(/#[^"]*$/, "", line)
    # Remove trailing spaces
    gsub(/[[:space:]]+$/, "", line)

    return line
}

# Return 1 if the array contains anything; otherwise return 0.
function anything(array,
                  ####
                  i) {
    for (i in array)
        if (array[i]) return 1
    return 0
}

# Append an element into an array (zero-based).
function append(array, element) {
    array[anything(array) ? length(array) : 0] = element
}

# Return an element if it belongs to the array;
# Otherwise, return a null string.
function belongsTo(element, array,
                   ####
                   i) {
    for (i in array)
        if (element == array[i]) return element
    return ""
}

# Return one of the substrings if the string starts with it;
# Otherwise, return a null string.
function startsWithAny(string, substrings,
                       ####
                       i) {
    for (i in substrings)
        if (index(string, substrings[i]) == 1) return substrings[i]
    return ""
}

# Return one of the patterns if the string matches this pattern at the beginning;
# Otherwise, return a null string.
function matchesAny(string, patterns,
                    ####
                    i) {
    for (i in patterns)
        if (string ~ "^" patterns[i]) return patterns[i]
    return ""
}

# Join an array into one string;
# Return the string.
function join(array, separator, sortedIn, preserveNull,
              ####
              i, j, saveSortedIn, temp) {
    # Default parameters
    if (!separator)
        separator = " "
    if (!sortedIn)
        sortedIn = "@ind_num_asc"

    temp = ""
    j = 0
    saveSortedIn = PROCINFO["sorted_in"]
    if (length(array)) {
        PROCINFO["sorted_in"] = sortedIn
        for (i in array)
            if (preserveNull || array[i] != "")
                temp = j++ ? temp separator array[i] : array[i]
        PROCINFO["sorted_in"] = saveSortedIn
    } else
        temp = array # array == ""

    return temp
}

# Initialize ANSI escape codes (ANSI X3.64 Standard Control Sequences).
# See: <https://en.wikipedia.org/wiki/ANSI_escape_code>
function initAnsiCode() {
    # Dumb terminal: no ANSI escape code whatsoever
    if (ENVIRON["TERM"] == "dumb") return

    AnsiCode["reset"]         = AnsiCode[0] = "\33[0m"

    AnsiCode["bold"]          = "\33[1m"
    AnsiCode["underline"]     = "\33[4m"
    AnsiCode["negative"]      = "\33[7m"
    AnsiCode["no bold"]       = "\33[22m" # SGR code 21 (bold off) not widely supported
    AnsiCode["no underline"]  = "\33[24m"
    AnsiCode["positive"]      = "\33[27m"

    AnsiCode["black"]         = "\33[30m"
    AnsiCode["red"]           = "\33[31m"
    AnsiCode["green"]         = "\33[32m"
    AnsiCode["yellow"]        = "\33[33m"
    AnsiCode["blue"]          = "\33[34m"
    AnsiCode["magenta"]       = "\33[35m"
    AnsiCode["cyan"]          = "\33[36m"
    AnsiCode["gray"]          = "\33[37m"

    AnsiCode["default"]       = "\33[39m"

    AnsiCode["dark gray"]     = "\33[90m"
    AnsiCode["light red"]     = "\33[91m"
    AnsiCode["light green"]   = "\33[92m"
    AnsiCode["light yellow"]  = "\33[93m"
    AnsiCode["light blue"]    = "\33[94m"
    AnsiCode["light magenta"] = "\33[95m"
    AnsiCode["light cyan"]    = "\33[96m"
    AnsiCode["white"]         = "\33[97m"
}

# Return ANSI escaped string.
function ansi(code, text) {
    switch (code) {
    case "bold":
        return AnsiCode[code] text AnsiCode["no bold"]
    case "underline":
        return AnsiCode[code] text AnsiCode["no underline"]
    case "negative":
        return AnsiCode[code] text AnsiCode["positive"]
    default:
        return AnsiCode[code] text AnsiCode[0]
    }
}

# Print warning message.
function w(text) {
    print ansi("yellow", text) > "/dev/stderr"
}

# Print error message.
function e(text) {
    print ansi("red", text) > "/dev/stderr"
}

# Print debugging message.
function d(text) {
    print ansi("gray", text) > "/dev/stderr"
}

# Log an array.
function da(array, formatString, sortedIn,
            ####
            i, j, saveSortedIn) {
    # Default parameters
    if (!formatString)
        formatString = "_[%s]='%s'"
    if (!sortedIn)
        sortedIn = "@ind_num_asc"

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = sortedIn
    for (i in array) {
        split(i, j, SUBSEP)
        d(sprintf(formatString, join(j, ","), array[i]))
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Naive assertion.
function assert(x, message) {
    if (!message)
        message = "[ERROR] Assertion failed."

    if (x)
        return x
    else
        e(message)
}

# Return non-zero if file exists, otherwise return 0.
function fileExists(file) {
    return !system("test -f " file)
}

# Initialize `UriSchemes`.
function initUriSchemes() {
    UriSchemes[0] = "file://"
    UriSchemes[1] = "http://"
    UriSchemes[2] = "https://"
}

BEGIN {
    initUrlEncoding()
    initAnsiCode()
    initUriSchemes()
}

####################################################################
# Commons.awk                                                      #
#                                                                  #
# Commonly used functions for string and array operations, logging.#
####################################################################

# Initialize `UrlEncoding`.
# See: <https://en.wikipedia.org/wiki/Percent-encoding>
function initUrlEncoding() {
    UrlEncoding["\n"] = "%0A"
    UrlEncoding[" "]  = "%20"
    UrlEncoding["!"]  = "%21"
    UrlEncoding["\""] = "%22"
    UrlEncoding["#"]  = "%23"
    UrlEncoding["$"]  = "%24"
    UrlEncoding["%"]  = "%25"
    UrlEncoding["&"]  = "%26"
    UrlEncoding["'"]  = "%27"
    UrlEncoding["("]  = "%28"
    UrlEncoding[")"]  = "%29"
    UrlEncoding["*"]  = "%2A"
    UrlEncoding["+"]  = "%2B"
    UrlEncoding[","]  = "%2C"
    UrlEncoding["-"]  = "%2D"
    UrlEncoding["."]  = "%2E"
    UrlEncoding["/"]  = "%2F"
    UrlEncoding[":"]  = "%3A"
    UrlEncoding[";"]  = "%3B"
    UrlEncoding["<"]  = "%3C"
    UrlEncoding["="]  = "%3D"
    UrlEncoding[">"]  = "%3E"
    UrlEncoding["?"]  = "%3F"
    UrlEncoding["@"]  = "%40"
    UrlEncoding["["]  = "%5B"
    UrlEncoding["\\"] = "%5C"
    UrlEncoding["]"]  = "%5D"
    UrlEncoding["^"]  = "%5E"
    UrlEncoding["_"]  = "%5F"
    UrlEncoding["`"]  = "%60"
    UrlEncoding["{"]  = "%7B"
    UrlEncoding["|"]  = "%7C"
    UrlEncoding["}"]  = "%7D"
    UrlEncoding["~"]  = "%7E"
}

# Return the real character represented by an escape sequence.
# Example: escapeChar("n") returns "\n".
# See: <https://en.wikipedia.org/wiki/Escape_character>
#      <https://en.wikipedia.org/wiki/Escape_sequences_in_C>
function escapeChar(char) {
    switch (char) {
    case "b":
        return "\b" # Backspace
    case "f":
        return "\f" # Formfeed
    case "n":
        return "\n" # Newline (Line Feed)
    case "r":
        return "\r" # Carriage Return
    case "t":
        return "\t" # Horizontal Tab
    case "v":
        return "\v" # Vertical Tab
    default:
        return char
    }
}

# Convert a literal-formatted string into its original string.
function literal(string,
                 ####
                 c, escaping, i, s) {
    if (string !~ /^".*"$/)
        return string

    split(string, s, "")
    string = ""
    escaping = 0
    for (i = 2; i < length(s); i++) {
        c = s[i]
        if (escaping) {
            string = string escapeChar(c)
            escaping = 0 # escape ends
        } else {
            if (c == "\\")
                escaping = 1 # escape begins
            else
                string = string c
        }
    }
    return string
}

# Return the escaped string.
function escape(string) {
    gsub(/"/, "\\\"", string)
    gsub(/\\/, "\\\\", string)
    return string
}

# Return the escaped, quoted string.
function parameterize(string, quotationMark) {
    if (!quotationMark)
        quotationMark = "'"

    if (quotationMark == "'") {
        gsub(/'/, "'\\''", string)
        return "'" string "'"
    } else {
        return "\"" escape(string) "\""
    }
}

# Return the URL-encoded string.
function quote(string,    i, r, s) {
    r = ""
    split(string, s, "")
    for (i = 1; i <= length(s); i++)
        r = r (s[i] in UrlEncoding ? UrlEncoding[s[i]] : s[i])
    return r
}

# Replicate a string.
function replicate(string, len,
                   ####
                   i, temp) {
    temp = ""
    for (i = 0; i < len; i++)
        temp = temp string
    return temp
}

# Squeeze a source line of AWK code.
function squeeze(line) {
    # Remove preceding spaces
    gsub(/^[[:space:]]+/, "", line)
    # Remove in-line comment
    gsub(/#[^"]*$/, "", line)
    # Remove trailing spaces
    gsub(/[[:space:]]+$/, "", line)

    return line
}

# Return 1 if the array contains anything; otherwise return 0.
function anything(array,
                  ####
                  i) {
    for (i in array)
        if (array[i]) return 1
    return 0
}

# Append an element into an array (zero-based).
function append(array, element) {
    array[anything(array) ? length(array) : 0] = element
}

# Return an element if it belongs to the array;
# Otherwise, return a null string.
function belongsTo(element, array,
                   ####
                   i) {
    for (i in array)
        if (element == array[i]) return element
    return ""
}

# Return one of the substrings if the string starts with it;
# Otherwise, return a null string.
function startsWithAny(string, substrings,
                       ####
                       i) {
    for (i in substrings)
        if (index(string, substrings[i]) == 1) return substrings[i]
    return ""
}

# Return one of the patterns if the string matches this pattern at the beginning;
# Otherwise, return a null string.
function matchesAny(string, patterns,
                    ####
                    i) {
    for (i in patterns)
        if (string ~ "^" patterns[i]) return patterns[i]
    return ""
}

# Join an array into one string;
# Return the string.
function join(array, separator, sortedIn, preserveNull,
              ####
              i, j, saveSortedIn, temp) {
    # Default parameters
    if (!separator)
        separator = " "
    if (!sortedIn)
        sortedIn = "@ind_num_asc"

    temp = ""
    j = 0
    saveSortedIn = PROCINFO["sorted_in"]
    if (length(array)) {
        PROCINFO["sorted_in"] = sortedIn
        for (i in array)
            if (preserveNull || array[i] != "")
                temp = j++ ? temp separator array[i] : array[i]
        PROCINFO["sorted_in"] = saveSortedIn
    } else
        temp = array # array == ""

    return temp
}

# Initialize ANSI escape codes (ANSI X3.64 Standard Control Sequences).
# See: <https://en.wikipedia.org/wiki/ANSI_escape_code>
function initAnsiCode() {
    # Dumb terminal: no ANSI escape code whatsoever
    if (ENVIRON["TERM"] == "dumb") return

    AnsiCode["reset"]         = AnsiCode[0] = "\33[0m"

    AnsiCode["bold"]          = "\33[1m"
    AnsiCode["underline"]     = "\33[4m"
    AnsiCode["negative"]      = "\33[7m"
    AnsiCode["no bold"]       = "\33[22m" # SGR code 21 (bold off) not widely supported
    AnsiCode["no underline"]  = "\33[24m"
    AnsiCode["positive"]      = "\33[27m"

    AnsiCode["black"]         = "\33[30m"
    AnsiCode["red"]           = "\33[31m"
    AnsiCode["green"]         = "\33[32m"
    AnsiCode["yellow"]        = "\33[33m"
    AnsiCode["blue"]          = "\33[34m"
    AnsiCode["magenta"]       = "\33[35m"
    AnsiCode["cyan"]          = "\33[36m"
    AnsiCode["gray"]          = "\33[37m"

    AnsiCode["default"]       = "\33[39m"

    AnsiCode["dark gray"]     = "\33[90m"
    AnsiCode["light red"]     = "\33[91m"
    AnsiCode["light green"]   = "\33[92m"
    AnsiCode["light yellow"]  = "\33[93m"
    AnsiCode["light blue"]    = "\33[94m"
    AnsiCode["light magenta"] = "\33[95m"
    AnsiCode["light cyan"]    = "\33[96m"
    AnsiCode["white"]         = "\33[97m"
}

# Print warning message.
function w(text) {
    print AnsiCode["yellow"] text AnsiCode[0] > "/dev/stderr"
}

# Print error message.
function e(text) {
    print AnsiCode["red"] AnsiCode["bold"] text AnsiCode[0] > "/dev/stderr"
}

# Print debugging message.
function d(text) {
    print AnsiCode["gray"] text AnsiCode[0] > "/dev/stderr"
}

# Log an array.
function da(array, formatString, sortedIn,
            ####
            i, j, saveSortedIn) {
    # Default parameters
    if (!formatString)
        formatString = "_[%s]='%s'"
    if (!sortedIn)
        sortedIn = "@ind_num_asc"

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = sortedIn
    for (i in array) {
        split(i, j, SUBSEP)
        d(sprintf(formatString, join(j, ","), array[i]))
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Naive assertion.
function assert(x, message) {
    if (!message)
        message = "[ERROR] Assertion failed."

    if (x)
        return x
    else
        e(message)
}

# Return non-zero if file exists, otherwise return 0.
function fileExists(file) {
    return !system("test -f " file)
}

# Initialize `UriSchemes`.
function initUriSchemes() {
    UriSchemes[0] = "file://"
    UriSchemes[1] = "http://"
    UriSchemes[2] = "https://"
}

BEGIN {
    initUrlEncoding()
    initAnsiCode()
    initUriSchemes()
}


Remove line count from commit line, since it's variable.

Consistently treat dates as seconds * 10.

BEGIN{
	revision_key=0
}

function gline()
{
	if(!getline)
		error("Unexpected EOF")
	return 1
}

function error(s)
{
	print s > "/dev/stderr"
	exit(1)
}

/^?/{next}

#Process a file entry
/^Working file:/{

	#Get the filename
	filename=$3

	#Find the tags
	while(gline() && !/^symbolic names:/);

	#Extract tags
	delete revision_to_tag
	while(gline() && ! /^keyword substitution:/)
	{
		tagname=substr($1, 1, length($1)-1)
		revision_to_tag[$2]=tagname
	}

	#Skip over the rest until the revisions
	while(gline() && !/^----------------/);

	#There is always at least one revision
	do
	{
		#The revision number
		gline()
		revision=$2

		#The commit line
		gline()
		author=substr($6, 1, length($6)-1)

		#Remove the variable line count.
		if($9 == "lines:")
		{
			$10 = ""
			$11 = ""
		}
		$0=$0
		commit_line=$0

		#Store the date* 10 for extra precision
		date = $2" "$3
		gsub(/[^0-9]+/, " ", date)
		date=mktime(date)*10

		#The commit message
		message=""
		while(gline() && !/^[-=]+$/)
			message = message $0 "\n"

		#The commit line is the primary key.

		#Keep track of commits
		date_to_line[date]=commit_line
		line_to_message[commit_line]=message
		line_to_author[commit_line]=author
		line_to_files[commit_line]=line_to_files[commit_line] " " filename
		line_to_revision[commit_line]=line_to_revision[commit_line] " " revision

		#The last file commit which has a certain tag
		#happens just before the tag.
		if(tag_to_date[revision_to_tag[revision]] < date)
			tag_to_date[revision_to_tag[revision]] = date
	}
	while(!/^=+$/)
}

END{
	#Delete the null tag for untagged revisions
	delete tag_to_date[""]

	#The last
	for(i in tag_to_date)
	{
		#Put the tag in just after the last commit
		date = tag_to_date[i] + 1
		date_to_line[date]="hello"
		date_to_tag[date]=i
	}

	#Sort commits by date
	num_dates=asorti(date_to_line, sorted_dates)

	#Iterate through all commits, newest first
	for(i=num_dates; i > 0; i--)
	{
		date = sorted_dates[i]

		if(date in date_to_tag)
		{
			print "=========================================="
			print "Repository tagged as " date_to_tag[date]
			print "=========================================="
		}
		else
		{
			line = date_to_line[date]

			author=line_to_author[line]
			message=line_to_message[line]

			file_list=line_to_files[line]
			rev_list=line_to_revision[line]

			print ""
			#Long commits can get spread over several seconds.
			#Try to concatenate entries which differ by a few
			#seconds, but have the same commit messsage and
			#author
			#We allow 20 seconds
			concatenation=""
			num_concat=0
			for(j=i-1; j>0 &&
					   author==line_to_author[date_to_line[sorted_dates[j]]] &&
					   message==line_to_message[date_to_line[sorted_dates[j]]] &&
					   date - sorted_dates[j] < 100; j--)
			{
				#Reset i, so that the fragment appears once only.
				i=j

				#Append to the file and revision list
				file_list=file_list " " line_to_files[date_to_line[sorted_dates[j]]];
				rev_list=rev_list " " line_to_revision[date_to_line[sorted_dates[j]]];

				#Make a note of teh concatenations
				concatenation=concatenation ", " (date - sorted_dates[j])/10 "s"
				num_concat++
			}

			#Create a message based on concatenation
			if(num_concat)
				concatenation = " (Concatenated " num_concat+1 " entries at " substr(concatenation, 3) ")"

			#Leader line
			print strftime("%a %e %b, %Y %H:%M:%S", date/10) " author: " author concatenation

			#List files commit happened on
			numfiles=split(file_list, files);
			split(rev_list, revisions);

			for(j=1; j <= numfiles; j++)
				print "    File: " files[j] " (Rev. " revisions[j] ")"

			#Print the commit message
			print "\n" message "\n--------------"
		}
	}
}

BEGIN{
	revision_key=0
}

function gline()
{
	if(!getline)
		error("Unexpected EOF")
	return 1
}

function error(s)
{
	print s > "/dev/stderr"
	exit(1)
}

/^?/{next}

#Process a file entry
/^Working file:/{

	#Get the filename
	filename=$3

	#Find the tags
	while(gline() && !/^symbolic names:/);

	#Extract tags
	delete revision_to_tag
	while(gline() && ! /^keyword substitution:/)
	{
		tagname=substr($1, 1, length($1)-1)
		revision_to_tag[$2]=tagname
	}

	#Skip over the rest until the revisions
	while(gline() && !/^----------------/);

	#There is always at least one revision
	do
	{
		#The revision number
		gline()
		revision=$2

		#The commit line
		gline()
		commit_line=$0
		author=substr($6, 1, length($6)-1)

		#Store the date* 10 for extra precision
		date = $2" "$3
		gsub(/[^0-9]+/, " ", date)
		date=mktime(date)*10

		#The commit message
		message=""
		while(gline() && !/^[-=]+$/)
			message = message $0 "\n"

		#The commit line is the primary key.

		#Keep track of commits
		date_to_line[date]=commit_line
		line_to_message[commit_line]=message
		line_to_author[commit_line]=author
		line_to_files[commit_line]=line_to_files[commit_line] " " filename
		line_to_revision[commit_line]=line_to_revision[commit_line] " " revision

		#The last file commit which has a certain tag
		#happens just before the tag.
		if(tag_to_date[revision_to_tag[revision]] < date)
			tag_to_date[revision_to_tag[revision]] = date
	}
	while(!/^=+$/)
}

END{
	#Delete the null tag for untagged revisions
	delete tag_to_date[""]

	#The last
	for(i in tag_to_date)
	{
		#Put the tag in just after the last commit
		date = tag_to_date[i] + 1
		date_to_line[date]="hello"
		date_to_tag[date]=i
	}

	#Sort commits by date
	num_dates=asorti(date_to_line, sorted_dates)

	#Iterate through all commits, newest first
	for(i=num_dates; i > 0; i--)
	{
		date = sorted_dates[i]

		if(date in date_to_tag)
		{
			print "=========================================="
			print "Repository tagged as " date_to_tag[date]
			print "=========================================="
		}
		else
		{
			line = date_to_line[date]

			author=line_to_author[line]
			message=line_to_message[line]

			file_list=line_to_files[line]
			rev_list=line_to_revision[line]

			print ""
			#Long commits can get spread over several seconds.
			#Try to concatenate entries which differ by a few
			#seconds, but have the same commit messsage and
			#author
			#We allow 20 seconds
			concatenation=""
			num_concat=0
			for(j=i-1; j>0 &&
					   author==line_to_author[date_to_line[sorted_dates[j]]] &&
					   message==line_to_message[date_to_line[sorted_dates[j]]] &&
					   date - sorted_dates[j] < 20; j--)
			{
				#Reset i, so that the fragment appears once only.
				i=j

				#Append to the file and revision list
				file_list=file_list " " line_to_files[date_to_line[sorted_dates[j]]];
				rev_list=rev_list " " line_to_revision[date_to_line[sorted_dates[j]]];

				#Make a note of teh concatenations
				concatenation=concatenation ", " date - sorted_dates[j] "s"
				num_concat++
			}

			#Create a message based on concatenation
			if(num_concat)
				concatenation = " (Concatenated " num_concat+1 " entries at " substr(concatenation, 3) ")"

			#Leader line
			print strftime("%a %e %b, %Y %H:%M:%S", date/10) " author: " author concatenation

			#List files commit happened on
			numfiles=split(file_list, files);
			split(rev_list, revisions);

			for(j=1; j <= numfiles; j++)
				print "    File: " files[j] " (Rev. " revisions[j] ")"

			#Print the commit message
			print "\n" message "\n--------------"
		}
	}
}


comments, & missing quote

comments, & missing quote

#! /usr/bin/gawk -f

#  Reduce the subject and object of RDF triples (ntriples format)
#  down to their @prefix (or literal object class)
#  Reduce predicates to the specific term identifier
#  and the ontology they are from. (curi form)
#  Express the subject and object as nodes with a directed edge
#  labeled with the predicate in the graphviz dot format
#  Include a tally of each combination of nodes and edge

#  This may over generalize in some cases because I do not have
#  a handy way to differentiate uri for subjects and objects
#  which may belong to a "structural" ontology (t-box) as opposed to
#  a data uri (a-box).

#  Post processing to augment predicate identifiers
#  with their labels seems to improve usefulness.

function usage(){
	print "usage: ntriple2dot.awk prefix_baseurl.yaml rdf.nt > dot.gv"
}

##########################################################
# remove first and last chars from input string
function trim(str){
	return substr(str,2,length(str)-2)
}

########################################################
# remove final(ish) element in paths with various delimiters
# this leaves the namespace of a removed local identifier

# this may need to be called more than once since
# some identifiers may have embedded delimiters other uri use differently
function stripid(uri){
	# <_:blanknode> are not allowed in ntriples (but may happen anyway)
	if(0 < match(uri, /^_:|^https:\/\/monarchinitiative.org\/.well-known\/genid\//))
		return "BNODE"
	# perspective endpoints, choose the longest
	delim["_"]=0; delim["/"]=0;
	delim["="]=0; delim[":"]=0; delim["#"]=0;
	char=""; max=-1;
	for(c in delim){
		l = match(uri, char)  # side effect is RLENGTH
		if(l > max){
			char = c;
			delim[char] = RLENGTH
		}
	}
	if(max<=0 || char=="")
		# we don't know what it is, a literal perhaps or uri fragment
		return uri
	else
		return substr(uri,1,delim[char])  # the probably truncated uri
}

# keep underscore, letters & numbers
# change the rest to (a single) underscore sans leading & trailing
# this passes valid node labels from dot's perspective (C-lang identifier rules)
function simplify(str){
	gsub(/[^[:alpha:][:digit:]_]+/,"_",str)
	gsub(/^_*|_*$/, "",str)
	gsub(/__*/,"_",str)
	return str
}

# if possible, find a shorter form for the input
function contract(uri){
	u = uri
	# shorten till longest uri in curi map is found (or not)
	while(!(u in prefix) &&  length(u))
		u = stripid(substr(u,1,length(u)-1))
	if(u in prefix)
		return prefix[u]
	else{
		# printf("WARN  %s\n", uri) > "/dev/stderr"
		return simplify(uri)
	}
}

# get the final (incl fragment identifier) portion of a slashed path
function final(uri){
	split(uri, b, "/")
	p = b[length(b)]
	anchor = match(p, "#")
	if(anchor > 0)
		p = substr(p, anchor+1)
	return p
}

BEGIN{
	# exceptions
	prefix["BNODE"]="BNODE"  # is a fixed point
	prefix["https://monarchinitiative.org/.well-known/genid"]="BNODE"
	prefid["https_archive_monarchinitiative_org_"]="MonarchArchive"
	############################################################
	# Often re-visit whether these exceptions are still necessary
	# they may have been moved into the curie prefix mapping file
	# or addressed in a local translation table
    # https://raw.githubusercontent.com/monarch-initiative/dipper/master/dipper/curie_map.yaml

	# in mgi
	prefix["https://www.mousephenotype.org"]="IMPC"
	# in IMPC (not httpS)
	prefix["http://www.mousephenotype.org"]="IMPC"
	# note in curie_map IMPC is:
	# http://www.mousephenotype.org/data/genes/

	# in panther
	# prefix["http://identifiers.org/wormbase/"]="WormBase"
	# note in curie_map WormBase is
	# 'https://www.wormbase.org/get?name='

	# in GWAScatalog
	prefix["http://www.ncbi.nlm.nih.gov/SNP/"]="dbSNP"
	# note in curie_map WormBase is
	# 'http://www.ncbi.nlm.nih.gov/projects/SNP/snp_ref.cgi?rs='

	# Ensembl  transient
	prefix["http://identifiers.org/ensembl/"]="ENSEMBL"
	# various, transient
    prefix["http://www.ncbi.nlm.nih.gov/gene/"]="NCBIGene"
	# kegg
	prefix["http://identifiers.org/ec-code/"]="EC"
	# ensembl
	prefix["http://identifiers.org/hgnc/HGNC:"]="HGNC"

	# playing with the idea of a LOCAL identifier
    # not the same as a bnode in that tools won't rewrite identifier
	# and their IRI are unroutable, i.e in same machine (or local net)
	# this is motivated in part by not wanting to publish
	# urls to our own servers which go nowhere.
	prefix["https://127.0.0.1/.well-known/genid"]="BNODE"  # exists, phase out
	prefix["http://0.0.0.0/.well-known/genid"]="BNODE"     # prefer
	prefix["http://0.0.0.0/"]="LCL"	                       # ID halfway house

	# LCL's obvious down side is zero options for global collisions
	# best you can do is go the uuid which is ugly, or
	# belive your obscure but readable string _is_ a special snowflake
}

# main loop
# parse and stash the curie yaml file (first file)
# YAML format is tic delimited word (the curi prefix)
# a colon, whitespace
# then a tic delimited url
# (FNR == NR) && /^'[^']*': 'http[^']*'.*/ { # loosing some?
(FNR == NR) && /^'.*/ {
	split($0, arr, "'")
	if(arr[2]=="")
		arr[2]="BASE"
	prefix[arr[4]]=arr[2]
}

# process the ntriple file(s)  which are not the first file

### subject predicate and object are all uri
(FNR != NR) && /^<[^>]*> <[^>]+> <[^>]+> \.$/ {
	s = contract(stripid(trim($1)))
	p =  final(trim($2))
	ns = contract(stripid(trim($2)))
	o = contract(stripid(trim($3)))
	edgelist[s,ns ":" p,o]++
	done[NR]=1
}
### subject & predicate are uri but the object is a literal
(FNR != NR) && /^<[^>]*> <[^>]*> "[^"]*".*\.$/ {
	s = contract(stripid(trim($1)))
	p = final(trim($2))
	ns = contract(stripid(trim($2)))
	edgelist[s, ns ":" p, "LITERAL"]++
	done[NR]=1
}
### subject is a bare blank node, predicate and object are uri
(FNR != NR) && /^_:[^ ]* <[^>]*> <[^>]*> \.$/ {
	# once our bnode syntax is uniform we may want more info out of it
	# s = contract(stripid($1))
	p =  final(trim($2))
	ns = contract(stripid(trim($2)))
	### Object (like subject)
	o = contract(stripid(trim($3)))
	edgelist["BNODE",ns ":" p,o]++
	done[NR]=1
}
### subject & predicate are uri & object is a bare blank node
(FNR != NR) && /^<[^>]*> <[^>]*> _:[^ ]* \.$/ {
	s = contract(stripid(trim($1)))
	p =  final(trim($2))
	ns = contract(stripid(trim($2)))
	#o = contract(stripid($3))
	edgelist[s,ns ":" p,"BNODE"]++
	done[NR]=1
}
### subject and object are a bare blank nodes, predicate is a uri
(FNR != NR) && /^_:[^ ]* <[^>]*> _:[^ ]* \.$/ {
	# s = contract(stripid($1))
	p =  final(trim($2))
	ns = contract(stripid(trim($2)))
	# o = contract(stripid($3))
	edgelist["BNODE",ns ":" p,"BNODE"]++
	done[NR]=1
}
### subject is a bare blank node, predicate is a uri & the object is a literal
(FNR != NR) && /^_:[^ ]* <[^>]*> "[^"]*".*\.$/ {
	# s = contract(stripid($1))
	p = final(trim($2))
	ns = contract(stripid(trim($2)))
	edgelist["BNODE", ns ":" p, "LITERAL"]++
	done[NR]=1
}

# print anything else so it makes itself known
(FNR != NR) && (done[NR]<1) && (NF>1){
	printf("ERROR? in %s at line %i:  %s\n",FILENAME,FNR,$0) > "/dev/stderr"
}

# output graphviz dot file, include edge counts
END{
	print "digraph {"
	print "rankdir=LR;"
	print "charset=\"utf-8\";"
	# sort the edges to facilitate future inter dot comparisons
	n=asorti(edgelist, sortlist)
	for (i=1; i<=n; i++) {
		edge=sortlist[i]
		split(edge ,spo, SUBSEP);
		print simplify(spo[1]) " -> " simplify(spo[3]) \
		" [label=\"" spo[2] " (" edgelist[edge] ")\"];"
	}
	print "LITERAL [shape=record];"
	print "labelloc=\"t\";"
	title = final(FILENAME)
	datestamp = strftime("%Y%m%d", systime())
	print "label=\"" substr(title,1,length(title)-3) " (" datestamp ")\";"
	print "}"
}

#! /usr/bin/gawk -f

#  Reduce the subject and object of RDF triples (ntriples format)
#  down to their @prefix (or literal object class)
#  Reduce predicates to the specific term identifier
#  and the ontology they are from. (curi form)
#  Express the subject and object as nodes with a directed edge
#  labeled with the predicate in the graphviz dot format
#  Include a tally of each combination of nodes and edge

#  This may over generalize in some cases because I do not have
#  a handy way to differentiate uri for subjects and objects
#  which may belong to a "structural" ontology (t-box) as opposed to
#  a data uri (a-box).

#  Post processing to augment predicate identifiers
#  with their labels seems to improve usefulness.

function usage(){
	print "usage: ntriple2dot.awk curie_map.yaml rdf.nt > rdf.dot"
}

##########################################################
# remove first and last chars from input string
function trim(str){
	return substr(str,2,length(str)-2)
}

########################################################
# remove final(ish) element in paths with various delimiters
# this leaves the namespace of a removed local identifier

# this may need to be called more than once since
# some identifiers may have embedded delimiters other uri use differently
function stripid(uri){
	# <_:blanknode> are not allowed in ntriples (but may happen anyway)
	if(0 < match(uri, /^_:|^https:\/\/monarchinitiative.org\/.well-known\/genid\//))
		return "BNODE"
	# perspective endpoints, choose the longest
	delim["_"]=0; delim["/"]=0;
	delim["="]=0; delim[":"]=0; delim["#"]=0;
	char=""; max=-1;
	for(c in delim){
		l = match(uri, char)  # side effect is RLENGTH
		if(l > max){
			char = c;
			delim[char] = RLENGTH
		}
	}
	if(max<=0 || char=="")
		# we don't know what it is, a literal perhaps or uri fragment
		return uri
	else
		return substr(uri,1,delim[char])  # the probably truncated uri
}

# keep underscore, letters & numbers
# change the rest to (a single) underscore sans leading & trailing
# this passes valid node labels from dot's perspective (C-lang identifier rules)
function simplify(str){
	gsub(/[^[:alpha:][:digit:]_]+/,"_",str)
	gsub(/^_*|_*$/, "",str)
	gsub(/__*/,"_",str)
	return str
}

# if possible, find a shorter form for the input
function contract(uri){
	u = uri
	# shorten till longest uri in curi map is found (or not)
	while(!(u in prefix) &&  length(u))
		u = stripid(substr(u,1,length(u)-1))
	if(u in prefix)
		return prefix[u]
	else{
		# printf("WARN  %s\n", uri) > "/dev/stderr"
		return simplify(uri)
	}
}

# get the final (incl fragment identifier) portion of a slashed path
function final(uri){
	split(uri, b, "/")
	p = b[length(b)]
	anchor = match(p, "#")
	if(anchor > 0)
		p = substr(p, anchor+1)
	return p
}

BEGIN{
	# exceptions
	prefix["BNODE"]="BNODE"  # is a fixed point
	prefix["https://monarchinitiative.org/.well-known/genid"]="BNODE"
	prefid["https_archive_monarchinitiative_org_"]="MonarchArchive
	############################################################
	# Often re-visit whether these exceptions are still necessary
	# they may have been moved into the curie prefix mapping file
	# or addressed in a local translation table
    # https://raw.githubusercontent.com/monarch-initiative/dipper/master/dipper/curie_map.yaml

	# in mgi
	prefix["https://www.mousephenotype.org"]="IMPC"
    # in IMPC (not httpS)
	prefix["http://www.mousephenotype.org"]="IMPC"
	# note in curie_map IMPC is:
	# http://www.mousephenotype.org/data/genes/

	# in panther
	# prefix["http://identifiers.org/wormbase/"]="WormBase"
	# note in curie_map WormBase is
	# 'https://www.wormbase.org/get?name='

	# in GWAScatalog
	prefix["http://www.ncbi.nlm.nih.gov/SNP/"]="dbSNP"
	# note in curie_map WormBase is
	# 'http://www.ncbi.nlm.nih.gov/projects/SNP/snp_ref.cgi?rs='

	# Ensembl  transient
	prefix["http://identifiers.org/ensembl/"]="ENSEMBL"
	# various, transient
    prefix["http://www.ncbi.nlm.nih.gov/gene/"]="NCBIGene"
	# kegg
	prefix["http://identifiers.org/ec-code/"]="EC"
	# ensembl
	prefix["http://identifiers.org/hgnc/HGNC:"]="HGNC"

	# playing with the idea of a LOCAL identifier
    # not the same as a bnode in that tools won't rewrite identifier
	# and their IRI are unroutable, i.e in same machine (or local net)
	# this is motivated in part by not wanting to publish
	# urls to our own servers which go nowhere.
	prefix["https://127.0.0.1/.well-known/genid"]="BNODE"  # exists, phase out
	prefix["http://0.0.0.0/.well-known/genid"]="BNODE"     # prefer
	prefix["http://0.0.0.0/"]="LCL"	                       # ID halfway house

	# LCL's obvious down side is zero options for global collisions
	# best you can do is go the uuid which is ugly, or
	# belive your obscure but readable string _is_ a special snowflake
}

# main loop
# parse and stash the curie yaml file (first file)
# YAML format is tic delimited word (the curi prefix)
# a colon, whitespace
# then a tic delimited url
# (FNR == NR) && /^'[^']*': 'http[^']*'.*/ { # loosing some?
(FNR == NR) && /^'.*/ {
	split($0, arr, "'")
	if(arr[2]=="")
		arr[2]="BASE"
	prefix[arr[4]]=arr[2]
}

# process the ntriple file(s)  which are not the first file

### subject predicate and object are all uri
(FNR != NR) && /^<[^>]*> <[^>]+> <[^>]+> \.$/ {
	s = contract(stripid(trim($1)))
	p =  final(trim($2))
	ns = contract(stripid(trim($2)))
	o = contract(stripid(trim($3)))
	edgelist[s,ns ":" p,o]++
	done[NR]=1
}
### subject & predicate are uri but the object is a literal
(FNR != NR) && /^<[^>]*> <[^>]*> "[^"]*".*\.$/ {
	s = contract(stripid(trim($1)))
	p = final(trim($2))
	ns = contract(stripid(trim($2)))
	edgelist[s, ns ":" p, "LITERAL"]++
	done[NR]=1
}
### subject is a bare blank node, predicate and object are uri
(FNR != NR) && /^_:[^ ]* <[^>]*> <[^>]*> \.$/ {
	# once our bnode syntax is uniform we may want more info out of it
	# s = contract(stripid($1))
	p =  final(trim($2))
	ns = contract(stripid(trim($2)))
	### Object (like subject)
	o = contract(stripid(trim($3)))
	edgelist["BNODE",ns ":" p,o]++
	done[NR]=1
}
### subject & predicate are uri & object is a bare blank node
(FNR != NR) && /^<[^>]*> <[^>]*> _:[^ ]* \.$/ {
	s = contract(stripid(trim($1)))
	p =  final(trim($2))
	ns = contract(stripid(trim($2)))
	#o = contract(stripid($3))
	edgelist[s,ns ":" p,"BNODE"]++
	done[NR]=1
}
### subject and object are a bare blank nodes, predicate is a uri
(FNR != NR) && /^_:[^ ]* <[^>]*> _:[^ ]* \.$/ {
	# s = contract(stripid($1))
	p =  final(trim($2))
	ns = contract(stripid(trim($2)))
	# o = contract(stripid($3))
	edgelist["BNODE",ns ":" p,"BNODE"]++
	done[NR]=1
}
### subject is a bare blank node, predicate is a uri & the object is a literal
(FNR != NR) && /^_:[^ ]* <[^>]*> "[^"]*".*\.$/ {
	# s = contract(stripid($1))
	p = final(trim($2))
	ns = contract(stripid(trim($2)))
	edgelist["BNODE", ns ":" p, "LITERAL"]++
	done[NR]=1
}

# print anything else so it makes itself known
(FNR != NR) && (done[NR]<1) && (NF>1){
	printf("ERROR? in %s at line %i:  %s\n",FILENAME,FNR,$0) > "/dev/stderr"
}

# output graphviz dot file, include edge counts
END{
	print "digraph {"
	print "rankdir=LR;"
	print "charset=\"utf-8\";"
	# sort the edges to facilitate future inter dot comparisons
	n=asorti(edgelist, sortlist)
	for (i=1; i<=n; i++) {
		edge=sortlist[i]
		split(edge ,spo, SUBSEP);
		print simplify(spo[1]) " -> " simplify(spo[3]) \
		" [label=\"" spo[2] " (" edgelist[edge] ")\"];"
	}
	print "LITERAL [shape=record];"
	print "labelloc=\"t\";"
	title = final(FILENAME)
	datestamp = strftime("%Y%m%d", systime())
	print "label=\"" substr(title,1,length(title)-3) " (" datestamp ")\";"
	print "}"
}


do not show empty lines

do not show empty lines

/fails as expected\.$/ {next}
/successfully loaded$/ {next}
/^ *<-.*loaded\.$/ {next}
/^->Loading import/ {next}
/^loading / {next}

# empty lines are dull
#
/^ *$/ {next}

{print}

/fails as expected\.$/ {next}
/successfully loaded$/ {next}
/^ *<-.*loaded\.$/ {next}
/^->Loading import/ {next}
/^loading / {next}

{print}