#!/usr/bin/perl
#
# NAME
#   csplit - split apart C routines into separate files.
#
# SYNOPSIS
#   csplit [options]..  [file]..
#
# DESCRIPTION
#   This program reads a chunk of C code, and  attempts  to  identify
#   the C routines. When it finds a function foo(), it creates a file
#   foo.c and writes the routine's code to it.
#
#   Any lines not written to such a .c file are  written  to  stdout.
#   Thus  this  program  can  be  used  as  a  filter  from vi for an
#   arbitrary chunk of code, and it will leave the unrecognized  code
#   unchanged.
#
# OPTIONS
#   Options may start with '+' or '-', for "enable" and "disable". So
#   far, the distinction isn't actually used.  Note that options must
#   all precede the file names; the first arg without a '-' or '+' is
#   treated as the first file name.
#
#   -I<file>
#
#   Include the line "#include <file>\n" in each file created.   Note
#   that  you  need  to  include the delimiters (<> or "") around the
#   file name, so you will usually need to quote the  entire  arg  to
#   hide the delimiters from your shell.  Thus '-I"abc2ps.h"' would
#   generate '#include "abc2ps.h"' lines in the files.
#
# FILES
#
# BUGS
#   This is an ad-hoc program that recognizes the functions that I've
#   seen in actual practice.  It doesn't do a complete parse of the C
#   code, which is necessary to do the job correctly.  So it'll  only
#   recognize "normal" function declarations.
#
# AUTHOR
#   John Chambers <jc@trillian.mit.edu>
#

@incl = ("#include \"abc2ps.h\"\n");	# For abc2ps.

$| = 1;
$exitstat = 0;
($me = $0) =~ s'.*/'';
$Vopt = $ENV{"V_$me"} || $ENV{"T_$me"} || $ENV{"D_$me"} || '1';
if ($Vopt =~ /^(\d)(.+)/) {	# Level and filename.
	$V = $1;
	$Vfil = $2;
	if (!open(V,">>$Vfil")) {
		print STDERR "$me: Can't write \"$Vfil\" ($!)\n" if $V>0;
		open(V,">>STDERR");
	}
} else {	# No file name.
	$V = $Vopt;
	open(V,">>STDERR");
}
select V; $| = 1; select STDOUT; $| = 1;
print V "$me started at verbose level $V ", `date` if $V>1;

args:
for $a (@ARGV) {
	if (($flg,$opt,$arg) = ($a =~ /^([-+])(\w)(.*)/)) {
		$a = '/dev/null';
		if ($opt eq 'I' || $opt eq 'i') {
			push(@incl,"#include $arg\n");
		} else {
			print STDERR "$me: Unknown option \"$a\" ignored.\n" if $V>0;
		}
	} else {
		last args;
	}
}

$T = '\w*\s*\**';	# Type.
$F = '\w+';			# Function name.

line:
while ($line = &nextline()) {
	if ($line =~ /^\s*$/) {	# Blank line?
		print @text if @text;
		@text = ();
		print $line;
		next line;
	}
	$line =~ s"/\*.*\*/\s*$"\n";	# Strip off comments
	if ($line =~ /^\s*($T)\s*($F)\s*\(([][\w\s,*]*)\)\s*$/) {	# type fct(...)
		$type = $1;
		$func = $2;
		$args = $3;
		&routine();
	} elsif ($line =~ /\s*($F)\s*\(([][\w\s,*]*)\)\s*$/) {	# type fct(...)
		$type = '';
		$func = $1;
		$args = $2;
		&routine();
	} elsif ($line =~ /^#\s*include\b/) {
		push(@incl, $line);
	} else {
		push(@text, $line);
	}
}
print @text if @text;

exit $exitstat;

sub routine {
	if (!open(F,">$func.c")) {
		print $V "$me: Can't write $func.c ($!)\n" if $V>0;
		push(@text, $line);
		return 0;
	}
	print F @incl if @incl;		# Includes.
	print F @text if @text;		# Header comments.
	print F $line if $line;		# Header itself.
	@text = ();
	while ($line = &nextline()) {
		print F $line;
		if ($line =~ /^}/) {
			return 1;
		}
	}
	close F;
}

sub nextline {
	$ll = <>;
	return $ll;
}
