#!/usr/bin/env perl eval '(exit $?0)' && eval 'exec perl -w -S $0 ${1+"$@"}' & eval 'exec perl -w -S $0 $argv:q' if 0; # # Splits C++ source files into one file per function or data item. # # Author: David L. Levine, with much help and encouragment from # Umar Syyid and Gonzalo A. Diethelm. # Completed by Andrew Gilpin, July 2000 # Date: 10 November 1998 # # For each C++ source file: # 1) Extracts the "intro" code, i.e., #includes and declarations. # 2) Identifies function definitions, relying on {, and } at the # beginning of a line, to delineate the function begin and # end. # # Assumptions: (applies only to the files being split, i.e. .cpp files) # * Function definition bodies are terminated with } appearing at # the beginning of a line. # * Free-standing (outside of functions) macro invocations must be # followed by a blank line, or terminated with a semicolon. # * A function must not have a blank line between its header # (signature) and its body. # * There aren't multiple C-style comments on one line, with code # between them. # * typedefs are on a single line # * A #endif doesn't have a multi-line C comment starting on that line. # The first three lines above let this script run without specifying the # full path to perl, as long as it is in the user's PATH. # Taken from perlrun man page. # Changes made by Andrew Gilpin (June - July 2000) # * Added option -c to use .c extension instead of .cpp extension # * Prints message when no filenames are specified on the command line # * Changed -? option to -h so that it works properly in most shells # * Added option -s to skip certain files, but copy them to $split_dir, # renaming them. (filename.cpp -> $split_dir/filename_S1.cpp). This is # here so that ACE can selectively not split certain files (namely those # that this script doesn't work with :) # * Added support for classes declared in the .cpp file. $usage="usage: $0 [-h] [-d] [-v] [-c] [-s filename] filenames\n"; #### Configuration parameters. $verbose = 0; $debug = 0; $split_dir = 'SPLIT'; $extension = 'cpp'; @files_to_skip = (); #### Constants. $DIR_SEPARATOR = $^O eq "MSWin32" ? '\\' : '/'; #### #### Process command line args. #### while ( $#ARGV >= $[ && $ARGV[0] =~ /^-/ ) { if ( $ARGV[0] eq '-d' ) { $debug = 1; } elsif ( $ARGV[0] eq '-v' ) { $verbose = 1; } elsif ( $ARGV[0] eq '-c' ) { $extension = 'c'; } elsif ( $ARGV[0] eq '-s' ) { push @files_to_skip, $ARGV[1]; shift; } elsif ( $ARGV[0] eq '-h' ) { print "$usage"; exit; } else { print STDERR "$0: unknown option $ARGV[0]\n"; die $usage; } shift; } &main (); #### #### Reset state, to process a new file starting with a clean slate. #### sub reset { #### Working data buffers. @intro = (); @current_comments = (); @current_code = (); @if = (); @save_if = (); @endif = (); @unknown = (); ####@unknown_s = (); #### State variables. $current_file_number = 0; $top_of_file = 1; $in_braces = 0; $in_nonfunction_code = 0; $in_C_comment = 0; $intro_length = 0; $preprocessor_continuation = 0; $preserved_ifs = 0; } sub main { #### Print error message if no files are specified. #### We need to do this before we modify anything on disk. die "No files specified!\n$usage" if (@ARGV == 0); #### Remove the destination subdirectory, if it exists. #### Attempts to clean it out using unlink may fail because #### it can have many files. if (-d "$split_dir") { system ("/bin/rm -r $split_dir") << 256 && die "$0: unable to rm \"$split_dir\"\n"; } #### Create the destination subdirectory. mkdir "$split_dir", 0755 || die "$0: unable to create $split_dir directory: $!\n"; MAIN_LOOP: foreach $file (@ARGV) { #### Strip off filename extension. ($basename = $file) =~ s/\.[^\.]+$//; foreach $skip_file (@files_to_skip) { if ($skip_file eq $file) { system ("/bin/cp $file $split_dir/" . $basename. "_S1\.$extension"); next MAIN_LOOP; } } &reset (); print "FILE: $file\n" if $verbose; open INPUT, "$file" || die "$0: unable to open \"$file\"\n"; while () { #### Strip comments from $line and use that for processing. #### But, use $_ for output, so that comments will be preserved. my $line = $_; #### If we're in the midst of a multiline C comment, see #### if it's finished on this line. if ($in_C_comment) { if ($line =~ s%^.*\*/%%) { #### End C-style comment. $in_C_comment = 0; if ($line =~ /^\s*$/ && ! $in_braces) { #### No code on the line. #&save_comment ($_); next; } } else { unless ($in_braces) { #&save_comment ($_); next; } } } #### Strip C++-style comments. if ($line =~ s%\s*//.*$%%) { if ($line =~ /^\s*$/ && ! $in_braces) { #### C++-style comment, without any code on the line. #&save_comment ($_); next; } } #### And C-style comments. if ($line =~ m%/\*%) { #### Begin C-style comment. Strip any complete comment(s), #### then see what's left. $line =~ s%\s*/\*.*\*/\s*%%g; #### check to see if a preprocessor is on this line if (! $in_braces) { if ($line eq '') { #### The line just had comment(s). Save it. #&save_comment ($_); next; } else { #### There's other text on the line. See if it's just the #### start of a comment. if ($line =~ m%/\*% && $line !~ m%\*/%) { #### The C-style comment isn't terminated on this line. $in_C_comment = 1; #&save_comment ($_); next; } } } } #### For now, skip ACE_RCSID's. Eventually, we might want to #### consider putting them in _every_ file, if they're enabled. next if $line =~ /^ACE_RCSID/; if ($in_braces) { push @unknown, $_; if ($line =~ /{/) { ++$in_braces; } elsif ($line =~ /^};/) { #### }; at beginning of line could signify end of class --$in_braces; if ($in_braces == 0) { push @intro, @unknown; @unknown = (); } } elsif ($line =~ /^}/) { #### } at beginning of line signifies end of function. --$in_braces; push @current_code, @unknown; @unknown = (); &finish_current ($basename, ++$current_file_number); } elsif ($line =~ /};/) { #### end of multi-line data delcaration --$in_braces; if ($in_braces == 0) { push @current_code, @unknown; @unknown = (); &finish_current ($basename, ++$current_file_number); } } } else { #### Not in braces. if (($line =~ m%[^/]*{%) && (! $preprocessor_continuation)) { #### { signifies beginning of braces (obviously :). if ($line =~ /};/) { #### braces end on this line push @unknown, $_; push @current_code, @unknown; @unknown = (); &finish_current ($basename, ++$current_file_number); } else { push @unknown, $_; $in_braces = 1; $in_nonfunction_code = $top_of_file = 0; } } elsif ($line =~ /^}/) { warn "$0: skipping unexpected } on line $. of \"$file\"\n"; next; } elsif ($line =~ /^typedef/) { push @intro, $_; } elsif ($line =~ /^\s*#/ || $preprocessor_continuation) { #### Preprocessor directive. if ($in_nonfunction_code) { push @unknown, $_; } else { push @intro, $_; } $top_of_file = 0; $preprocessor_continuation = /\\$/ ? 1 : 0; if ($line =~ m%^\s*#\s*if\s*(.*)(/.*)*$%) { push @save_if, $_; unshift @endif, "#endif /* $1 [Added by split-cpp.] */\n"; } elsif ($line =~ /^\s*#\s*endif/) { #### End an #if/#else block. unless (defined pop @save_if) { pop @if; if ($preserved_ifs > 0) { --$preserved_ifs; } } shift @endif; #### } elsif ($line =~ /^\s*#/) { #### Any other preprocessor directive. } } elsif ($line =~ /^\s*$/) { #### Whitespace only, or empty line.. push @current_code, "\n"; if ($in_nonfunction_code) { #### In the midst of non-function code, we reached a #### blank line. Assume that we're done with it. &finish_current ($basename, ++$current_file_number); } else { #### Not in a function, so add to intro. Just in case data or #### a function follow it, flush now. $preserved_ifs += $#save_if + 1; &flush_current (\@intro); } } elsif ($line =~ /;/) { #### Data definition or semicolon-terminated macro invocation. push @unknown, $_; $top_of_file = 0; #### Is it file-static? Squash newlines out of @current_code. my $statement = join (' ', @current_code); if ($statement =~ /([^=[(]+)[=[(](.*)/) { if ($1 =~ /static/) { #### Move code to the intro. push @intro, @current_comments; @current_comments = (); &flush_current (\@intro); #### Not separate code. $in_nonfunction_code = 0; #### ???? Extract name from the left side and save for #### later matching. } else { if ($statement =~ /^USEUNIT\s*\(/) { #### Special-case those Borland USEUNIT things. &flush_current (\@intro); } else { #### Non-static entity, with semicolon. Wrap it up. push @current_code, @unknown; @unknown = (); &finish_current ($basename, ++$current_file_number); } } } else { #### Dunno. Wrap it up, anyways. push @current_code, @unknown; @unknown = (); &finish_current ($basename, ++$current_file_number); } } else { #### Beginning of data definition or function or class. push @unknown, $_; $in_nonfunction_code = 1; $top_of_file = 0; } } if (eof) { close (ARGV); #### To reset line number counter. if ($#intro > $intro_length) { #### Leftover prepreprocessor statement(s), such as #pragma #### instantiate. &finish_current ($basename, ++$current_file_number); } } } close INPUT; } }; #### #### Save a comment in the appropriate array. #### #sub save_comment { # my ($comment) = @_; # # if ($top_of_file) { # push @intro, $comment; # } else { # push @current_comments, $comment; # } #} #### #### Flush the contents of the @current_code array to the destination #### argument array. It is passed by reference. #### sub flush_current { my ($destination) = @_; push @$destination, @current_code; @current_code = (); } #### #### Flush what we've got now to an output (split) file. #### sub finish_current { my ($basename, $current_file_number) = @_; my $current_file_name = sprintf "$split_dir$DIR_SEPARATOR${basename}_S%d.$extension", $current_file_number++; if ($verbose) { print "CURRENT OUTPUT FILE: $current_file_name\n"; print "INTRO:\n"; print @intro; print @if; print @current_comments; print "CURRENT CODE:\n"; print @current_code; print @endif; } open OUTPUT, "> $current_file_name" || die "unable to open $current_file_name\n"; print OUTPUT "// Automatically generated by ACE's split-cpp.\n" . "// DO NOT EDIT!\n\n"; if ($debug) { print OUTPUT "INTRO:\n", @intro, "IF:\n", @if, "COMMENTS:\n", @current_comments, "CURRENT:\n", @current_code, "ENDIF:\n", @endif; } else { print OUTPUT @intro, @if, @current_comments, @current_code, @endif; } close OUTPUT; #### For detection of leftover preprocessor statements and #### comments at end of file. $intro_length = $#intro; @current_comments = @current_code = @save_if = (); $in_braces = $in_nonfunction_code = 0; }