<commit_before>#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Std;
use File::Compare;
use Text::Wrap;

our $base;
our %outfiles;
our %types;
our %procSets;
our %procNames;

END {
	my $status = $?;
	my $file;
	foreach $file (values %outfiles) {
		unlink("$file.$$");
	}
	# Special case: temporary file which is never promoted to a real
	# output file
	unlink("$base.x.$$")
		if $base;
	$? = $status;
}

sub parseErr {
	my $file = shift;
	my $line = shift;
	my $msg = shift;
	
	print(STDERR "$file, line $line: $msg\n");
	exit 1;
}

sub openFile {
	my $handle = shift;
	my $name = shift;
	
	open($handle, ">", "$name.$$") || die "Can't open $name.$$";
	$outfiles{$handle} = $name;
	print $handle "/* AUTOGENERATED FILE -- DO NOT EDIT */\n\n";
}

sub closeFiles {
	my $handle;
	my $file;
	
	while (($handle, $file) = each %outfiles) {
		close($handle);
		if (!compare("$file.$$", $file)) {
			unlink("$file.$$");
		} else {
			rename("$file.$$", $file) || die "Couldn't write $file";
		}
		delete $outfiles{$handle};
	}
}

sub wrapc {
	my $input = shift;
	
	my $output;
	my $line;
	
	local($Text::Wrap::columns) = 80;
	local($Text::Wrap::huge) = "overflow";
	# Only break at argument boundaries
	local($Text::Wrap::break) = qr/, /;
	# ...and re-add the comma afterward.  Newer versions of Text::Wrap use
	# separator2 for newly-added line breaks; older versions just use
	# separator.  Test is formatted strangely since local() declaration
	# can't be made inside a block
	local($Text::Wrap::separator2) = ",\n"
		if defined($Text::Wrap::separator2);
	local($Text::Wrap::separator) = ",\n"
		if !defined($Text::Wrap::separator2);

	foreach $line (split(/\n/, $input)) {
		$output .= Text::Wrap::wrap("", "\t\t\t", $line) . "\n";
	}
	return $output;
}

sub argument {
	my $type = shift;
	my $var = shift;
	
	if ($type ne "void") {
		return ", $type $var";
	} else {
		return "";
	}
}

sub parameter {
	my $type = shift;
	my $param = shift;
	
	if ($type ne "void") {
		return $param;
	} else {
		return "NULL";
	}
}

sub opt_parameter {
	my $type = shift;
	my $param = shift;
	
	if ($type ne "void") {
		return ", $param";
	} else {
		return "";
	}
}

sub typesize {
	my $type = shift;
	
	if ($type ne "void") {
		return "sizeof($type)";
	} else {
		return "0";
	}
}

# Sort hash keys numerically: 0..MAX, -1..MIN
sub opcodeSort {
	my $hash = shift;
	
	my @nums;
	
	@nums = sort {$a <=> $b} grep ($_ >= 0, keys %$hash);
	@nums = (@nums, sort {$b <=> $a} grep ($_ < 0, keys %$hash));
	return @nums;
}

sub gen_sender_stub_sync_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $in = shift;
	my $out = shift;
	
	my $inarg = argument($in, "*in");
	my $outarg = argument($out, "**out");
	my $inparam = parameter($in, "in");
	my $outparam = parameter($out, "out");
	
	print $fh wrapc(<<EOF);

int ${base}_$func(struct mrpc_connection *conn$inarg$outarg)
{
	return mrpc_send_request(&${base}_$role, conn, nr_${base}_$func, $inparam, $outparam);
}
EOF
}

sub gen_sender_stub_sync_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;
	my $out = shift;
	
	my $inarg = argument($in, "*in");
	my $outarg = argument($out, "**out");
	
	print $fh wrapc(<<EOF);
int ${base}_$func(struct mrpc_connection *conn$inarg$outarg);
EOF
}

sub gen_sender_stub_typedef_h {
	my $fh = shift;
	my $func = shift;
	my $out = shift;
	
	my $outarg = argument($out, "*reply");
	
	print $fh wrapc(<<EOF);
typedef void (${base}_${func}_callback_fn)(void *conn_private, void *msg_private, int status$outarg);
EOF
}

sub gen_sender_stub_async_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $in = shift;
	
	my $inarg = argument($in, "*in");
	my $inparam = parameter($in, "in");
	
	print $fh wrapc(<<EOF);

int ${base}_${func}_async(struct mrpc_connection *conn, ${base}_${func}_callback_fn *callback, void *private$inarg)
{
	return mrpc_send_request_async(&${base}_$role, conn, nr_${base}_$func, $inparam, callback, private);
}
EOF
}

sub gen_sender_stub_async_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;
	
	my $inarg = argument($in, "*in");
	
	print $fh wrapc(<<EOF);
int ${base}_${func}_async(struct mrpc_connection *conn, ${base}_${func}_callback_fn *callback, void *private$inarg);
EOF
}

sub gen_receiver_stub_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $out = shift;
	
	my $outarg = argument($out, "*out");
	my $outparam = parameter($out, "out");
	
	print $fh wrapc(<<EOF);

int ${base}_${func}_send_async_reply(struct mrpc_message *request, int status$outarg)
{
	return mrpc_send_reply(&${base}_$role, request, status, $outparam);
}
EOF
}

sub gen_receiver_stub_h {
	my $fh = shift;
	my $func = shift;
	my $out = shift;
	
	my $outarg = argument($out, "*out");
	
	print $fh wrapc(<<EOF);
int ${base}_${func}_send_async_reply(struct mrpc_message *request, int status$outarg);
EOF
}

sub gen_oneway_stub_c {
	my $fh = shift;
	my $func = shift;
	my $in = shift;
	
	my $inarg = argument($in, "*in");
	my $inparam = parameter($in, "in");
	
	print $fh wrapc(<<EOF);

int ${base}_${func}(struct mrpc_connection *conn$inarg)
{
	return mrpc_send_request_noreply(conn, nr_${base}_$func, $inparam);
}
EOF
}

sub gen_oneway_stub_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;
	
	my $inarg = argument($in, "*in");
	
	print $fh wrapc(<<EOF);
int ${base}_${func}(struct mrpc_connection *conn$inarg);
EOF
}

sub gen_free_proc_c {
	my $fh = shift;
	my $type = shift;
	
	print $fh wrapc(<<EOF);

void free_$type($type *in, int container)
{
	xdr_free((xdrproc_t)xdr_$type, (char *)in);
	if (container)
		free(in);
}
EOF
}

sub gen_free_proc_h {
	my $fh = shift;
	my $type = shift;
	
	print $fh wrapc(<<EOF);
void free_$type($type *in, int container);
EOF
}

sub gen_request_proc {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;
	
	my $num;
	my $func;
	my $in;
	my $out;
	my $inparam;
	my $outparam;
	
	print $fh wrapc(<<EOF);

static int ${base}_${role}_request(void *p_ops, void *conn_data, struct mrpc_message *msg, int cmd, void *in, void *out)
{
	struct ${base}_${role}_operations *ops=p_ops;
	
	if (ops == NULL)
		return MINIRPC_PROCEDURE_UNAVAIL;
	
	switch (cmd) {
EOF
	
	foreach $num (opcodeSort($procs)) {
		($func, $in, $out) = @{$procs->{$num}}[2..4];
		$inparam = opt_parameter($in, "in");
		$outparam = opt_parameter($out, "out");
		print $fh wrapc(<<EOF);
	case nr_${base}_$func:
		if (ops->$func == NULL)
			return MINIRPC_PROCEDURE_UNAVAIL;
EOF
		if ($num >= 0) {
			print $fh wrapc(<<EOF);
		else
			return ops->$func(conn_data, msg$inparam$outparam);
EOF
		} else {
			print $fh wrapc(<<EOF);
		else {
			ops->$func(conn_data, msg$inparam);
			return MINIRPC_OK;
		}
EOF
		}
	}
	
	print $fh wrapc(<<EOF);
	default:
		return MINIRPC_PROCEDURE_UNAVAIL;
	}
}
EOF
}

sub gen_info_proc {
	my $fh = shift;
	my $role = shift;
	my $isReply = shift;
	my $procs = shift;
	
	my $reply = $isReply ? "reply" : "request";
	my $num;
	my $func;
	my $type;
	my $typesize;
	
	print $fh wrapc(<<EOF);

static int ${base}_${role}_${reply}_info(unsigned cmd, xdrproc_t *type, unsigned *size)
{
	switch (cmd) {
EOF
	
	foreach $num (opcodeSort($procs)) {
		next if $isReply and $num < 0;
		$func = @{$procs->{$num}}[2];
		$type = @{$procs->{$num}}[$isReply ? 4 : 3];
		$typesize = typesize($type);
		print $fh wrapc(<<EOF);
	case nr_${base}_$func:
		SET_PTR_IF_NOT_NULL(type, (xdrproc_t)xdr_$type);
		SET_PTR_IF_NOT_NULL(size, $typesize);
		return MINIRPC_OK;
EOF
	}
	
	print $fh wrapc(<<EOF);
	default:
		return MINIRPC_PROCEDURE_UNAVAIL;
	}
}
EOF
}

sub gen_opcode_enum {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;
	
	my $num;
	my $func;
	
	return if (keys %$procs == 0);
	print $fh "\nenum ${base}_${role}_procedures {\n";
	foreach $num (opcodeSort($procs)) {
		$func = @{$procs->{$num}}[2];
		print $fh "\tnr_${base}_$func = $num,\n";
	}
	print $fh "};\n";
}

sub gen_operations_struct {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;
	
	my $num;
	my $func;
	my $in;
	my $out;
	my $inarg;
	my $outarg;
	my $retType;
	
	return if (keys %$procs == 0);
	print $fh "\nstruct ${base}_${role}_operations {\n";
	foreach $num (opcodeSort($procs)) {
		($func, $in, $out) = @{$procs->{$num}}[2..4];
		$inarg = argument($in, "*in");
		$outarg = argument($out, "*out");
		$retType = ($num >= 0) ? "int" : "void";
		print $fh wrapc("\t$retType (*$func)(void *conn_data, struct mrpc_message *msg$inarg$outarg);");
	}
	print $fh "};\n";
}

sub gen_set_operations_c {
	my $fh = shift;
	my $role = shift;
	
	print $fh wrapc(<<EOF);

int ${base}_${role}_set_operations(struct mrpc_connection *conn, struct ${base}_${role}_operations *ops)
{
	return mrpc_conn_set_operations(conn, ${base}_$role, ops);
}
EOF
}

sub gen_set_operations_h {
	my $fh = shift;
	my $role = shift;
	
	print $fh wrapc(<<EOF);

int ${base}_${role}_set_operations(struct mrpc_connection *conn, struct ${base}_${role}_operations *ops);
EOF
}

sub gen_protocol_struct_c {
	my $fh = shift;
	my $role = shift;
	
	my $antirole = ($role eq "client") ? "server" : "client";
	my $isServer = ($role eq "server") ? "1" : "0";
	
	print $fh wrapc(<<EOF);

struct mrpc_protocol ${base}_$role = {
	.is_server = $isServer,
	.request = ${base}_${role}_request,
	.sender_request_info = ${base}_${role}_request_info,
	.sender_reply_info = ${base}_${role}_reply_info,
	.receiver_request_info = ${base}_${antirole}_request_info,
	.receiver_reply_info = ${base}_${antirole}_reply_info
};
EOF
}

sub gen_protocol_struct_h {
	my $fh = shift;
	my $role = shift;
	
	print $fh wrapc(<<EOF);

extern struct mrpc_protocol ${base}_$role;
EOF
}

sub genstubs_sync {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;
	
	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;
	
	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_sync_c($cf, $role, $func, $arg, $ret);
		gen_sender_stub_sync_h($hf, $func, $arg, $ret);
	}
}

sub genstubs_sender_async {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;
	
	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;
	
	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_async_c($cf, $role, $func, $arg);
		gen_sender_stub_typedef_h($hf, $func, $ret);
	}
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_async_h($hf, $func, $arg);
	}
}

sub genstubs_receiver_async {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;
	
	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;
	
	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_receiver_stub_c($cf, $role, $func, $ret);
		gen_receiver_stub_h($hf, $func, $ret);
	}
}

sub genstubs_noreply {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;
	
	my @keys;
	my $num;
	my $func;
	my $arg;
	
	@keys = sort {$b <=> $a} grep ($_ < 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg) = @{$procs->{$num}}[2..4];
		gen_oneway_stub_c($cf, $func, $arg);
		gen_oneway_stub_h($hf, $func, $arg);
	}
}

sub genstubs_free {
	my $cf = shift;
	my $hf = shift;
	
	my @typelist;
	my $type;
	
	@typelist = grep(!/^void$/, sort keys %types);
	return if !@typelist;
	print $hf "\n";
	foreach $type (@typelist) {
		gen_free_proc_c($cf, $type);
		gen_free_proc_h($hf, $type);
	}
}

sub genstubs {
	my $role;
	my $procs;
	my $file;
	my $line;
	my $num;
	my $func;
	my $arg;
	my $ret;
	my $hf;
	
	# Validate procedure definitions
	foreach $role ("server", "client") {
		$procs = $procSets{$role};
		foreach $num (opcodeSort($procs)) {
			($file, $line, $func, $arg, $ret) = @{$procs->{$num}};
			parseErr($file, $line, "No such type: $arg")
				if !defined($types{$arg});
			parseErr($file, $line, "No such type: $ret")
				if !defined($types{$ret});
			parseErr($file, $line, "Procedures in ${role}msgs " .
						"section cannot return a value")
				if $num < 0 && $ret ne "void";
			print "$func($arg, $ret) = $num\n";
		}
	}
	
	# Create output files
	openFile(*MCF, "${base}_minirpc.c");
	openFile(*MHF, "${base}_minirpc.h");
	openFile(*CHF, "${base}_client.h");
	openFile(*SHF, "${base}_server.h");
	
	# Generate cpp directives
	print MCF "#define MINIRPC_PROTOCOL\n";
	print MCF "#include \"minirpc_protocol.h\"\n";
	print MCF "#include \"${base}_client.h\"\n";
	print MCF "#include \"${base}_server.h\"\n";
	print MHF "#ifndef " . uc $base . "_MINIRPC_H\n";
	print MHF "#define " . uc $base . "_MINIRPC_H\n\n";
	print MHF "#include \"${base}_xdr.h\"\n";
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		print $hf "#ifndef " . uc "${base}_${role}_H" . "\n";
		print $hf "#define " . uc "${base}_${role}_H" . "\n\n";
		print $hf "#include \"${base}_minirpc.h\"\n";
	}
	
	# Generate toplevel structures
	foreach $role ("server", "client") {
		gen_opcode_enum(*MHF, $role, $procSets{$role});
	}
	foreach $role ("server", "client") {
		gen_info_proc(*MCF, $role, 0, $procSets{$role});
		gen_info_proc(*MCF, $role, 1, $procSets{$role});
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		gen_operations_struct($hf, $role, $procSets{$role});
	}
	foreach $role ("server", "client") {
		gen_request_proc(*MCF, $role, $procSets{$role});
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		gen_protocol_struct_c(*MCF, $role);
		gen_protocol_struct_h($hf, $role);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		gen_set_operations_c(*MCF, $role);
		gen_set_operations_h($hf, $role);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_sync($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_sender_async($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		genstubs_receiver_async($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_noreply($role, $procSets{$role}, *MCF, $hf);
	}
	genstubs_free(*MCF, *MHF);
	
	foreach $hf (*MHF, *CHF, *SHF) {
		print $hf "\n#endif\n";
	}
}

sub parseFile {
	my $infile = shift;
	
	my $filename;
	my $line;
	my $data;
	my $curProcData;
	my $curDefs;
	my $noreply;
	my $sym_re = '([a-zA-Z0-9_]+)';
	my $type_re = '((unsigned\s+)?[a-zA-Z0-9_]+)';
	my $func;
	my $num;
	
	$data=`cpp $infile`;
	die "Couldn't open $infile"
		if $?;
	$filename = $infile;
	$line = 0;
	foreach $_ (split /\n/, $data) {
		$line++;
		if (/^# ([1-9][0-9]*) "([^"]+)"/) {
			# cpp line marker
			$filename = $2;
			$line = $1 - 1;
		}
		if (!$curDefs) {
			if (/^\s*(client|server)(procs|msgs)\s+{/) {
				$curDefs = $1;
				$noreply = ($2 eq "msgs");
				$procSets{$curDefs} = {}
					if !exists($procSets{$curDefs});
				next;
			}
			if (/^\s*(struct|enum)\s+$sym_re\s+{/o) {
				print "Found $1 $2\n";
				$types{$2} = 1;
			}
			if (/^\s*typedef\s+$sym_re\s+$type_re[<\[]?/o) {
				print "Found typedef $2\n";
				$types{$2} = 1;
			}
		} else {
			if (/}/) {
				undef $curDefs;
				next;
			}
			if (/^\s*$sym_re\(($type_re(,\s+$type_re)?)?\)\s*=
						\s*([1-9][0-9]*)\s*;/ox) {
				$func = $1;
				$num = $8;
				$num = -$num
					if $noreply;
				# file, line, func, arg, ret
				$curProcData = [$filename, $line, $func,
							$3 ? $3 : "void",
							$6 ? $6 : "void"];
				parseErr($filename, $line, "Duplicate " .
							"procedure number")
					if defined($procSets{$curDefs}->{$num});
				parseErr($filename, $line, "Duplicate " .
							"procedure name")
					if defined($procNames{$func});
				$procSets{$curDefs}->{$num} = $curProcData;
				$procNames{$func} = 1;
			} elsif (/^\s*$/) {
				next;
			} else {
				parseErr($filename, $line, "Invalid syntax");
			}
		}
	}
}

sub genRpclFile {
	my @files = shift;
	
	my $inDefs = 0;
	my $infile;
	
	open(XF, ">", "${base}.x.$$") || die "Can't open ${base}.x.$$";
	for $infile (@files) {
		open(FH, "<", $infile) || die "Can't open $infile";
		while (<FH>) {
			if (!$inDefs) {
				if (/^\s*(client|server)(procs|msgs)\s+{/) {
					$inDefs = 1;
					print XF "\n";
				} else {
					print XF;
				}
			} else {
				$inDefs = 0
					if (/}/);
				print XF "\n";
			}
		}
	}
	close(XF);
}


our $opt_o;
getopts("o:");
if (!defined($opt_o) || @ARGV == 0) {
	print "Usage: $0 -o <output_file_base_name> <input_files>\n";
	exit 1;
}
$base = $opt_o;

# Initialize primitive types
# These are the primitive types that can appear as procedure parameters.
# Right now we only support void, as a special case, because we don't want
# to make assumptions about the native (unserialized) length of the data
# types that XDR produces.  Regular types ("int", "bool", etc.) can still
# be used with XDR typedefs, because sizeof(some_typedef) will do the right
# thing (as opposed to sizeof(hyper), which is the only thing we could do
# since we don't know what C type "hyper" corresponds to).
$types{"void"} = 1;

# Preprocess the input files with cpp and parse them
my $infile;
for $infile (@ARGV) {
	parseFile($infile);
}

# Generate stubs
genstubs;

# Read the input files again, this time without cpp, and generate a .x file
# suitable for parsing with rpcgen.  Try to preserve line numbers.
genRpclFile(@ARGV);

# Generate xdr.c
open(IF, "-|", "rpcgen -c $base.x.$$") or
	die "Couldn't generate ${base}_xdr.c";
openFile(*XCF, "${base}_xdr.c");
while (<IF>) {
	s/${base}\.x\.h/${base}_xdr.h/
		if /#include/;
	print XCF;
}

# Generate xdr.h
my $olddefine = uc "_$base.x_H_RPCGEN";
my $newdefine = uc "${base}_XDR_H";
open(IF, "-|", "rpcgen -h $base.x.$$") or
	die "Couldn't generate ${base}_xdr.h";
openFile(*XHF, "${base}_xdr.h");
while (<IF>) {
	s/$olddefine/$newdefine/;
	print XHF;
}

# Commit output
closeFiles();
<commit_msg>minirpcgen: Compile fixes for generated code<commit_after>#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Std;
use File::Compare;
use Text::Wrap;

our $base;
our %outfiles;
our %types;
our %procSets;
our %procNames;

END {
	my $status = $?;
	my $file;
	foreach $file (values %outfiles) {
		unlink("$file.$$");
	}
	# Special case: temporary file which is never promoted to a real
	# output file
	unlink("$base.x.$$")
		if $base;
	$? = $status;
}

sub parseErr {
	my $file = shift;
	my $line = shift;
	my $msg = shift;
	
	print(STDERR "$file, line $line: $msg\n");
	exit 1;
}

sub openFile {
	my $handle = shift;
	my $name = shift;
	
	open($handle, ">", "$name.$$") || die "Can't open $name.$$";
	$outfiles{$handle} = $name;
	print $handle "/* AUTOGENERATED FILE -- DO NOT EDIT */\n\n";
}

sub closeFiles {
	my $handle;
	my $file;
	
	while (($handle, $file) = each %outfiles) {
		close($handle);
		if (!compare("$file.$$", $file)) {
			unlink("$file.$$");
		} else {
			rename("$file.$$", $file) || die "Couldn't write $file";
		}
		delete $outfiles{$handle};
	}
}

sub wrapc {
	my $input = shift;
	
	my $output;
	my $line;
	
	local($Text::Wrap::columns) = 80;
	local($Text::Wrap::huge) = "overflow";
	# Only break at argument boundaries
	local($Text::Wrap::break) = qr/, /;
	# ...and re-add the comma afterward.  Newer versions of Text::Wrap use
	# separator2 for newly-added line breaks; older versions just use
	# separator.  Test is formatted strangely since local() declaration
	# can't be made inside a block
	local($Text::Wrap::separator2) = ",\n"
		if defined($Text::Wrap::separator2);
	local($Text::Wrap::separator) = ",\n"
		if !defined($Text::Wrap::separator2);

	foreach $line (split(/\n/, $input)) {
		$output .= Text::Wrap::wrap("", "\t\t\t", $line) . "\n";
	}
	return $output;
}

sub argument {
	my $type = shift;
	my $var = shift;
	
	if ($type ne "void") {
		return ", $type $var";
	} else {
		return "";
	}
}

sub parameter {
	my $type = shift;
	my $param = shift;
	
	if ($type ne "void") {
		return $param;
	} else {
		return "NULL";
	}
}

sub opt_parameter {
	my $type = shift;
	my $param = shift;
	
	if ($type ne "void") {
		return ", $param";
	} else {
		return "";
	}
}

sub typesize {
	my $type = shift;
	
	if ($type ne "void") {
		return "sizeof($type)";
	} else {
		return "0";
	}
}

# Sort hash keys numerically: 0..MAX, -1..MIN
sub opcodeSort {
	my $hash = shift;
	
	my @nums;
	
	@nums = sort {$a <=> $b} grep ($_ >= 0, keys %$hash);
	@nums = (@nums, sort {$b <=> $a} grep ($_ < 0, keys %$hash));
	return @nums;
}

sub gen_sender_stub_sync_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $in = shift;
	my $out = shift;
	
	my $inarg = argument($in, "*in");
	my $outarg = argument($out, "**out");
	my $inparam = parameter($in, "in");
	my $outparam = parameter($out, "out");
	
	print $fh wrapc(<<EOF);

int ${base}_$func(struct mrpc_connection *conn$inarg$outarg)
{
	return mrpc_send_request(&${base}_$role, conn, nr_${base}_$func, $inparam, $outparam);
}
EOF
}

sub gen_sender_stub_sync_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;
	my $out = shift;
	
	my $inarg = argument($in, "*in");
	my $outarg = argument($out, "**out");
	
	print $fh wrapc(<<EOF);
int ${base}_$func(struct mrpc_connection *conn$inarg$outarg);
EOF
}

sub gen_sender_stub_typedef_h {
	my $fh = shift;
	my $func = shift;
	my $out = shift;
	
	my $outarg = argument($out, "*reply");
	
	print $fh wrapc(<<EOF);
typedef void (${base}_${func}_callback_fn)(void *conn_private, void *msg_private, int status$outarg);
EOF
}

sub gen_sender_stub_async_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $in = shift;
	
	my $inarg = argument($in, "*in");
	my $inparam = parameter($in, "in");
	
	print $fh wrapc(<<EOF);

int ${base}_${func}_async(struct mrpc_connection *conn, ${base}_${func}_callback_fn *callback, void *private$inarg)
{
	return mrpc_send_request_async(&${base}_$role, conn, nr_${base}_$func, callback, private, $inparam);
}
EOF
}

sub gen_sender_stub_async_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;
	
	my $inarg = argument($in, "*in");
	
	print $fh wrapc(<<EOF);
int ${base}_${func}_async(struct mrpc_connection *conn, ${base}_${func}_callback_fn *callback, void *private$inarg);
EOF
}

sub gen_receiver_stub_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $out = shift;
	
	my $outarg = argument($out, "*out");
	my $outparam = parameter($out, "out");
	
	print $fh wrapc(<<EOF);

int ${base}_${func}_send_async_reply(struct mrpc_message *request, int status$outarg)
{
	return mrpc_send_reply(&${base}_$role, request, status, $outparam);
}
EOF
}

sub gen_receiver_stub_h {
	my $fh = shift;
	my $func = shift;
	my $out = shift;
	
	my $outarg = argument($out, "*out");
	
	print $fh wrapc(<<EOF);
int ${base}_${func}_send_async_reply(struct mrpc_message *request, int status$outarg);
EOF
}

sub gen_oneway_stub_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $in = shift;
	
	my $inarg = argument($in, "*in");
	my $inparam = parameter($in, "in");
	
	print $fh wrapc(<<EOF);

int ${base}_${func}(struct mrpc_connection *conn$inarg)
{
	return mrpc_send_request_noreply(&${base}_$role, conn, nr_${base}_$func, $inparam);
}
EOF
}

sub gen_oneway_stub_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;
	
	my $inarg = argument($in, "*in");
	
	print $fh wrapc(<<EOF);
int ${base}_${func}(struct mrpc_connection *conn$inarg);
EOF
}

sub gen_free_proc_c {
	my $fh = shift;
	my $type = shift;
	
	print $fh wrapc(<<EOF);

void free_$type($type *in, int container)
{
	xdr_free((xdrproc_t)xdr_$type, (char *)in);
	if (container)
		free(in);
}
EOF
}

sub gen_free_proc_h {
	my $fh = shift;
	my $type = shift;
	
	print $fh wrapc(<<EOF);
void free_$type($type *in, int container);
EOF
}

sub gen_request_proc {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;
	
	my $num;
	my $func;
	my $in;
	my $out;
	my $inparam;
	my $outparam;
	
	print $fh wrapc(<<EOF);

static int ${base}_${role}_request(void *p_ops, void *conn_data, struct mrpc_message *msg, int cmd, void *in, void *out)
{
	struct ${base}_${role}_operations *ops=p_ops;
	
	if (ops == NULL)
		return MINIRPC_PROCEDURE_UNAVAIL;
	
	switch (cmd) {
EOF
	
	foreach $num (opcodeSort($procs)) {
		($func, $in, $out) = @{$procs->{$num}}[2..4];
		$inparam = opt_parameter($in, "in");
		$outparam = opt_parameter($out, "out");
		print $fh wrapc(<<EOF);
	case nr_${base}_$func:
		if (ops->$func == NULL)
			return MINIRPC_PROCEDURE_UNAVAIL;
EOF
		if ($num >= 0) {
			print $fh wrapc(<<EOF);
		else
			return ops->$func(conn_data, msg$inparam$outparam);
EOF
		} else {
			print $fh wrapc(<<EOF);
		else {
			ops->$func(conn_data, msg$inparam);
			return MINIRPC_OK;
		}
EOF
		}
	}
	
	print $fh wrapc(<<EOF);
	default:
		return MINIRPC_PROCEDURE_UNAVAIL;
	}
}
EOF
}

sub gen_info_proc {
	my $fh = shift;
	my $role = shift;
	my $isReply = shift;
	my $procs = shift;
	
	my $reply = $isReply ? "reply" : "request";
	my $num;
	my $func;
	my $type;
	my $typesize;
	
	print $fh wrapc(<<EOF);

static int ${base}_${role}_${reply}_info(unsigned cmd, xdrproc_t *type, unsigned *size)
{
	switch (cmd) {
EOF
	
	foreach $num (opcodeSort($procs)) {
		next if $isReply and $num < 0;
		$func = @{$procs->{$num}}[2];
		$type = @{$procs->{$num}}[$isReply ? 4 : 3];
		$typesize = typesize($type);
		print $fh wrapc(<<EOF);
	case nr_${base}_$func:
		SET_PTR_IF_NOT_NULL(type, (xdrproc_t)xdr_$type);
		SET_PTR_IF_NOT_NULL(size, $typesize);
		return MINIRPC_OK;
EOF
	}
	
	print $fh wrapc(<<EOF);
	default:
		return MINIRPC_PROCEDURE_UNAVAIL;
	}
}
EOF
}

sub gen_opcode_enum {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;
	
	my $num;
	my $func;
	
	return if (keys %$procs == 0);
	print $fh "\nenum ${base}_${role}_procedures {\n";
	foreach $num (opcodeSort($procs)) {
		$func = @{$procs->{$num}}[2];
		print $fh "\tnr_${base}_$func = $num,\n";
	}
	print $fh "};\n";
}

sub gen_operations_struct {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;
	
	my $num;
	my $func;
	my $in;
	my $out;
	my $inarg;
	my $outarg;
	my $retType;
	
	print $fh "\nstruct ${base}_${role}_operations {\n";
	foreach $num (opcodeSort($procs)) {
		($func, $in, $out) = @{$procs->{$num}}[2..4];
		$inarg = argument($in, "*in");
		$outarg = argument($out, "*out");
		$retType = ($num >= 0) ? "int" : "void";
		print $fh wrapc("\t$retType (*$func)(void *conn_data, struct mrpc_message *msg$inarg$outarg);");
	}
	print $fh "};\n";
}

sub gen_set_operations_c {
	my $fh = shift;
	my $role = shift;
	
	print $fh wrapc(<<EOF);

int ${base}_${role}_set_operations(struct mrpc_connection *conn, struct ${base}_${role}_operations *ops)
{
	return mrpc_conn_set_operations(conn, &${base}_$role, ops);
}
EOF
}

sub gen_set_operations_h {
	my $fh = shift;
	my $role = shift;
	
	print $fh wrapc(<<EOF);

int ${base}_${role}_set_operations(struct mrpc_connection *conn, struct ${base}_${role}_operations *ops);
EOF
}

sub gen_protocol_struct_c {
	my $fh = shift;
	my $role = shift;
	
	my $antirole = ($role eq "client") ? "server" : "client";
	my $isServer = ($role eq "server") ? "1" : "0";
	
	print $fh wrapc(<<EOF);

struct mrpc_protocol ${base}_$role = {
	.is_server = $isServer,
	.request = ${base}_${role}_request,
	.sender_request_info = ${base}_${role}_request_info,
	.sender_reply_info = ${base}_${role}_reply_info,
	.receiver_request_info = ${base}_${antirole}_request_info,
	.receiver_reply_info = ${base}_${antirole}_reply_info
};
EOF
}

sub gen_protocol_struct_h {
	my $fh = shift;
	my $role = shift;
	
	print $fh wrapc(<<EOF);

extern struct mrpc_protocol ${base}_$role;
EOF
}

sub genstubs_sync {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;
	
	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;
	
	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_sync_c($cf, $role, $func, $arg, $ret);
		gen_sender_stub_sync_h($hf, $func, $arg, $ret);
	}
}

sub genstubs_sender_async {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;
	
	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;
	
	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_async_c($cf, $role, $func, $arg);
		gen_sender_stub_typedef_h($hf, $func, $ret);
	}
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_async_h($hf, $func, $arg);
	}
}

sub genstubs_receiver_async {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;
	
	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;
	
	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_receiver_stub_c($cf, $role, $func, $ret);
		gen_receiver_stub_h($hf, $func, $ret);
	}
}

sub genstubs_noreply {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;
	
	my @keys;
	my $num;
	my $func;
	my $arg;
	
	@keys = sort {$b <=> $a} grep ($_ < 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg) = @{$procs->{$num}}[2..4];
		gen_oneway_stub_c($cf, $role, $func, $arg);
		gen_oneway_stub_h($hf, $func, $arg);
	}
}

sub genstubs_free {
	my $cf = shift;
	my $hf = shift;
	
	my @typelist;
	my $type;
	
	@typelist = grep(!/^void$/, sort keys %types);
	return if !@typelist;
	print $hf "\n";
	foreach $type (@typelist) {
		gen_free_proc_c($cf, $type);
		gen_free_proc_h($hf, $type);
	}
}

sub genstubs {
	my $role;
	my $procs;
	my $file;
	my $line;
	my $num;
	my $func;
	my $arg;
	my $ret;
	my $hf;
	
	# Validate procedure definitions
	foreach $role ("server", "client") {
		$procs = $procSets{$role};
		foreach $num (opcodeSort($procs)) {
			($file, $line, $func, $arg, $ret) = @{$procs->{$num}};
			parseErr($file, $line, "No such type: $arg")
				if !defined($types{$arg});
			parseErr($file, $line, "No such type: $ret")
				if !defined($types{$ret});
			parseErr($file, $line, "Procedures in ${role}msgs " .
						"section cannot return a value")
				if $num < 0 && $ret ne "void";
			print "$func($arg, $ret) = $num\n";
		}
	}
	
	# Create output files
	openFile(*MCF, "${base}_minirpc.c");
	openFile(*MHF, "${base}_minirpc.h");
	openFile(*CHF, "${base}_client.h");
	openFile(*SHF, "${base}_server.h");
	
	# Generate cpp directives
	print MCF "#define MINIRPC_PROTOCOL\n";
	print MCF "#include \"minirpc_protocol.h\"\n";
	print MCF "#include \"${base}_client.h\"\n";
	print MCF "#include \"${base}_server.h\"\n";
	print MHF "#ifndef " . uc $base . "_MINIRPC_H\n";
	print MHF "#define " . uc $base . "_MINIRPC_H\n\n";
	print MHF "#include \"${base}_xdr.h\"\n";
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		print $hf "#ifndef " . uc "${base}_${role}_H" . "\n";
		print $hf "#define " . uc "${base}_${role}_H" . "\n\n";
		print $hf "#include \"${base}_minirpc.h\"\n";
	}
	
	# Generate toplevel structures
	foreach $role ("server", "client") {
		gen_opcode_enum(*MHF, $role, $procSets{$role});
		gen_info_proc(*MCF, $role, 0, $procSets{$role});
		gen_info_proc(*MCF, $role, 1, $procSets{$role});
	}
	foreach $role ("server", "client") {
		gen_request_proc(*MCF, $role, $procSets{$role});
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		gen_protocol_struct_c(*MCF, $role);
		gen_protocol_struct_h($hf, $role);
	}
	foreach $role ("server", "client") {
		next if !keys %{$procSets{$role}};
		$hf = ($role eq "server") ? *SHF : *CHF;
		gen_operations_struct($hf, $role, $procSets{$role});
		gen_set_operations_c(*MCF, $role);
		gen_set_operations_h($hf, $role);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_sync($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_sender_async($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		genstubs_receiver_async($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_noreply($role, $procSets{$role}, *MCF, $hf);
	}
	genstubs_free(*MCF, *MHF);
	
	foreach $hf (*MHF, *CHF, *SHF) {
		print $hf "\n#endif\n";
	}
}

sub parseFile {
	my $infile = shift;
	
	my $filename;
	my $line;
	my $data;
	my $curProcData;
	my $curDefs;
	my $noreply;
	my $sym_re = '([a-zA-Z0-9_]+)';
	my $type_re = '((unsigned\s+)?[a-zA-Z0-9_]+)';
	my $func;
	my $num;
	
	$data=`cpp $infile`;
	die "Couldn't open $infile"
		if $?;
	$filename = $infile;
	$line = 0;
	foreach $_ (split /\n/, $data) {
		$line++;
		if (/^# ([1-9][0-9]*) "([^"]+)"/) {
			# cpp line marker
			$filename = $2;
			$line = $1 - 1;
		}
		if (!$curDefs) {
			if (/^\s*(client|server)(procs|msgs)\s+{/) {
				$curDefs = $1;
				$noreply = ($2 eq "msgs");
				$procSets{$curDefs} = {}
					if !exists($procSets{$curDefs});
				next;
			}
			if (/^\s*(struct|enum)\s+$sym_re\s+{/o) {
				print "Found $1 $2\n";
				$types{$2} = 1;
			}
			if (/^\s*typedef\s+$sym_re\s+$type_re[<\[]?/o) {
				print "Found typedef $2\n";
				$types{$2} = 1;
			}
		} else {
			if (/}/) {
				undef $curDefs;
				next;
			}
			if (/^\s*$sym_re\(($type_re(,\s+$type_re)?)?\)\s*=
						\s*([1-9][0-9]*)\s*;/ox) {
				$func = $1;
				$num = $8;
				$num = -$num
					if $noreply;
				# file, line, func, arg, ret
				$curProcData = [$filename, $line, $func,
							$3 ? $3 : "void",
							$6 ? $6 : "void"];
				parseErr($filename, $line, "Duplicate " .
							"procedure number")
					if defined($procSets{$curDefs}->{$num});
				parseErr($filename, $line, "Duplicate " .
							"procedure name")
					if defined($procNames{$func});
				$procSets{$curDefs}->{$num} = $curProcData;
				$procNames{$func} = 1;
			} elsif (/^\s*$/) {
				next;
			} else {
				parseErr($filename, $line, "Invalid syntax");
			}
		}
	}
}

sub genRpclFile {
	my @files = shift;
	
	my $inDefs = 0;
	my $infile;
	
	open(XF, ">", "${base}.x.$$") || die "Can't open ${base}.x.$$";
	for $infile (@files) {
		open(FH, "<", $infile) || die "Can't open $infile";
		while (<FH>) {
			if (!$inDefs) {
				if (/^\s*(client|server)(procs|msgs)\s+{/) {
					$inDefs = 1;
					print XF "\n";
				} else {
					print XF;
				}
			} else {
				$inDefs = 0
					if (/}/);
				print XF "\n";
			}
		}
	}
	close(XF);
}


our $opt_o;
getopts("o:");
if (!defined($opt_o) || @ARGV == 0) {
	print "Usage: $0 -o <output_file_base_name> <input_files>\n";
	exit 1;
}
$base = $opt_o;

# Initialize primitive types
# These are the primitive types that can appear as procedure parameters.
# Right now we only support void, as a special case, because we don't want
# to make assumptions about the native (unserialized) length of the data
# types that XDR produces.  Regular types ("int", "bool", etc.) can still
# be used with XDR typedefs, because sizeof(some_typedef) will do the right
# thing (as opposed to sizeof(hyper), which is the only thing we could do
# since we don't know what C type "hyper" corresponds to).
$types{"void"} = 1;

# Preprocess the input files with cpp and parse them
my $infile;
for $infile (@ARGV) {
	parseFile($infile);
}

# Generate stubs
genstubs;

# Read the input files again, this time without cpp, and generate a .x file
# suitable for parsing with rpcgen.  Try to preserve line numbers.
genRpclFile(@ARGV);

# Generate xdr.c
open(IF, "-|", "rpcgen -c $base.x.$$") or
	die "Couldn't generate ${base}_xdr.c";
openFile(*XCF, "${base}_xdr.c");
while (<IF>) {
	s/${base}\.x\.h/${base}_xdr.h/
		if /#include/;
	print XCF;
}

# Generate xdr.h
my $olddefine = uc "_$base.x_H_RPCGEN";
my $newdefine = uc "${base}_XDR_H";
open(IF, "-|", "rpcgen -h $base.x.$$") or
	die "Couldn't generate ${base}_xdr.h";
openFile(*XHF, "${base}_xdr.h");
while (<IF>) {
	s/$olddefine/$newdefine/;
	print XHF;
}

# Commit output
closeFiles();

{'fraction_non_alphanumeric': 0.20488222934866748, 'fraction_numerical': 0.004561539312175163, 'mean_word_length': 3.8430847503012453, 'pattern_counts': {'":': 0, '<': 129, '<?xml version=': 0, '>': 73, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3355094', 'n_tokens_mistral': 16128, 'n_tokens_neox': 14871, 'n_words': 3807}