#! /usr/bin/perl #/*@@ # @file cpp.pl # @date Wed Sep 15 14:21:53 1999 # @author Tom Goodale # @desc # Replacement C pre-processor # @enddesc # @version $Header$ #@@*/ #$debug=1; ############################################################################### ############################################################################### # Setup some global variables. # Version info $version = "Cactus CPP 1.0"; # Symbol table %defines = (); #Initial symbols &Define("__FILE__", "\"replace-me\"", "
",__LINE__); &Define("__LINE__", "\"replace-me\"", "
",__LINE__); # Current working directory for opening files. $current_wd = "."; # Include path @include_path = (); # Filename and linenumber stacks for error traces. @filelist = (); @linelist = (); # Complete list of included files for generating dependencies %complete_file_list = (); # Are we in the middle of a comment ? $incomment = 0; # Number of non-fatal errors encountered $errorcount = 0; ############################################################################### ############################################################################### my $empty_args_warning = 1; my $empty_args_error = 0; # Parse the command line ($source_file, $output_file, $do_deps, @include_path) = &ParseCommandLine(@ARGV); ############################################################################### ############################################################################### #If no source file given, choose stdin if(! $source_file) { $source_file = "-" } # Setup output stream if($output_file && $output_file ne "-") { open(OUTSTREAM,">$output_file") || die "Unable to open output file"; } else { *OUTSTREAM = STDOUT; } ############################################################################### ############################################################################### # Parse the input. &ProcessFile($source_file, "-", -1, 1-$do_deps); ############################################################################### ############################################################################### # Do Dependency generation if requested if($do_deps) { my $file; my $depend_target; $source_file =~ m,^.+/([^/]+)$,; if($1) { $depend_target = "$1.o"; } else { $depend_target = "$source_file.o"; } foreach $file (sort keys %complete_file_list) { # Ignore any empry entries next if($file =~ m/^\s*$/); # The source file depends upon this file print OUTSTREAM "$depend_target : $file\n"; } foreach $file (sort keys %complete_file_list) { # Ignore any empry entries next if($file =~ m/^\s*$/); # Generate empty rule for file so can delete header files without problems print OUTSTREAM "$file :\n"; } } if ($errorcount > 0) { # There were non-fatal errors exit 1; } # There were no errors exit; ############################################################################### ############################################################################### ############################################################################### ############################################################################### #/*@@ # @routine ParseCommandLine # @date Wed Sep 15 14:22:28 1999 # @author Tom Goodale # @desc # Checks the command line for options # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub ParseCommandLine { my(@args) = @_; my($source_file, $output_file, $do_deps, @include_path) = (0,0,0,()); while($arg = shift(@args)) { if($arg =~ m:^-I(.*):) { push(@include_path, $1); } elsif($arg =~ m:^-D([^=]+)(=)?(.*):) { &Define($1, $3,"command-line",0); } elsif($arg =~ m:^-M(.*):) { $do_deps = 1; } elsif($arg =~ m:^--version:) { print "$version\n"; exit 0; } elsif($arg =~ m:^-.+:) { die("Unknown preprocessor option '$arg'"); } elsif($source_file && $output_file) { die("Source and output files already set"); } elsif($source_file) { $output_file = $arg; } else { $source_file = $arg; } } return ($source_file, $output_file, $do_deps, @include_path); } ############################################################################### #/*@@ # @routine ProcessFile # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Open a file and parse its contents. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub ProcessFile { my ($newfilename, $oldfilename, $oldlinenumber, $printline) = @_; local *FILEDESC; my $fullpath; my $new_current_wd; ($new_current_wd,$fullpath) = &FindFile($newfilename,$current_wd,\@include_path); # Override this variable on the stack local $current_wd = $new_current_wd; if($newfilename ne "-" && !$fullpath) { die "Unable to find $newfilename included at $oldfilename:$oldlinenumber"; } if($newfilename ne "-") { if($debug) { print "Opening $newfilename\n"; } open(FILEDESC, "< $fullpath") || die "Unable to open file $fullpath"; push(@filelist, $oldfilename); push(@linelist, $oldlinenumber); } else { $newfilename = ""; $current_wd = "."; *FILEDESC = STDIN; } # If we are not printing lines, must being doing deps, so save file name if($newfilename ne "-" && ! $printline ) { $complete_file_list{"$fullpath"} = 1; } &ParseFile(FILEDESC,$newfilename,0,1,$printline); if($newfilename ne "-") { if($debug) { print "Closing $newfilename\n"; } close(FILEDESC); pop(@filelist); pop(@linelist); } } ############################################################################### #/*@@ # @routine FindFile # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Finds a file and works out its full name and the directory its in. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub FindFile { my($newfilename,$old_current_wd,$ra_include_path) = @_; my $fullpath; my $new_current_wd; if($newfilename =~ m,^/,) { #absolute path $fullpath = $newfilename; } elsif($old_current_wd && -r "$old_current_wd/$newfilename") { $fullpath = "$old_current_wd/$newfilename"; } else { for(my $dir=0; $dir < @$ra_include_path; $dir++) { if(-r "$ra_include_path->[$dir]/$newfilename") { $fullpath = "$ra_include_path->[$dir]/$newfilename"; last; } } } # Tidy up the path a bit # Get rid of /./ in paths $fullpath =~ s,/\./,/,g; # Get new current wd. if($fullpath) { $fullpath =~ m,^(.+)/[^/]+$,; $new_current_wd = $1; } $fullpath =~ s,^./,,; return ($new_current_wd, $fullpath); } ############################################################################### #/*@@ # @routine ParseFile # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Parse part or all of a file. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub ParseFile { local *INFILE = $_[0]; my $filename = $_[1]; my $linenumber = $_[2]; my $active = $_[3]; my $printline = $_[4]; my $firstline = $linenumber; my $retcode = 0; my $line; my $currentline; if($debug) { print "Entered ParseFile: $filename:$linenumber, active=$active\n"; } if($printline && $active && $linenumber==0) { print "# 1 \"$filename\"\n"; } while(1) { ($line, $linenumber) = &ReadLine(*INFILE,$linenumber); $currentline = $line; # Exit loop if file is finished last if(! defined($line)); # If it isn't a preprocessor command, just process it if($line !~ m/^\s*\#/) { if($active) { my $retval; ($expanded,$retval) = &ParseAndExpand($line, "STDIN", $linenumber); print OUTSTREAM "$expanded\n" if $printline; $retcode += $retval; } next; } if($line=~m/^\s*#\s*define\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\(.*?\))?)(\s+(.*))?/) { # Define a macro &Define($1,$3,$filename, $linenumber) if($active); next; } elsif($line =~ m/^\s*#\s*undef\s+([^\s]+)/) { # Undefine a macro &UnDefine($1,$filename,$linenumber) if($active); next; } elsif($line =~ m/^\s*#\s*if(.+)/) { #Deal with a #if clause - do it recursively my $newactive; # Parse the if statement and see if the first clause is active if($active) { $newactive = &ProcessIf($1, $filename, $linenumber,$printline); } else { #If not active before, still inactive $newactive = 0; } my $beenactive = $newactive; my $foundelse = 0; # Now process first clause and any #elif or #else clauses while(1) { # Parse the clause ($currentline, $linenumber) = &ParseFile(*INFILE,$filename,$linenumber,$newactive && $active,$printline); if(! $currentline) { # Got EOF ! die "Unexpected EOF when parsing $filename"; } elsif($currentline && $currentline =~ /\#\s*endif\s*/) { # Finished last; } elsif($currentline =~ m/^\s*#\s*elif(\s+.+)/ && ! $foundelse) { # Got #elif, is this next clause active ? if(! $beenactive) { if($active) { $newactive = &ProcessIf($1, $filename, $linenumber); } else { $newactive = 0; } $beenactive = $newactive; } else { $newactive = 0; } } elsif($currentline =~ m/^\s*#\s*else\s*$/ && ! $foundelse) { # Got #else, have any of the clauses been active ? if($active) { $newactive = 1 - $beenactive } else { $newactive = 0; } $foundelse = 1; } else { if($currentline =~ m/^\s*#\s*else/ || $currentline =~ m/^\s*#\s*elsif/) { print STDERR "Extraneous #else of #elsif found at $filename:$linenumber\n"; $newactive = 0; } else { die "Unexpected line '$currentline' at $filename:$linenumber"; } } } } elsif($line =~ m/^\s*\#\s*elif/ || $line =~ m/^\s*\#\s*else/ || $line =~ m/^\s*\#\s*endif/) { if($firstline > 0) { # If we are processing just part of the file, ok last; } else { # Otherwise there's an extra one here die "Unexpected #elif/#else/#endif at $filename:$linenumber"; } } elsif($line =~ m/^\s*#\s*include\s+(.+)?/) { # Now to include files. if(! defined($1)) { print STDERR "Missing argument to #include directive at $filename:$linenumber\n"; } else { if($active) { my $argument = $1; # Allow people to use macros to define name of include file ($argument,undef) = &ParseAndExpand($argument,$filename,$linenumber); if($argument !~ m/\s*(\"|<)(.+)(\"|>)\s*$/) { print STDERR "Invalid filename $argument in #include directive at $filename:$linenumber\n"; } else { # Process the new file. # Don't need to pass $active since wouldn't be here if inactive. # # Silently ignore files which are included via '#include
' # but couldn't be found in the include path. my $token = $1; my ($dummy, $fullpath) = &FindFile($2,$current_wd,\@include_path); if (-r $fullpath or $token eq '"') { &ProcessFile($2,$filename,$linenumber,$printline); } if($printline && $active) { print "# $linenumber \"$filename\"\n"; } } } } } else { print STDERR "Unrecognised # directive at $filename:$linenumber\n" } } if($debug) { print "Leaving ParseFile : currentline = '$currentline', linenumber = $linenumber\n"; } return ($currentline,$linenumber); } ############################################################################### #/*@@ # @routine ReadLine # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Read a line from the current file descriptor. # Deals with comments and continuation lines # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub ReadLine { local *INFILE = $_[0]; my $linenumber = $_[1]; my $line; ($line,$linenumber) = &GetNextLine(*INFILE,$linenumber); # Deal with C-style comments # Deal with completely enclosed comments $line =~ s,/\*.*\*/, ,g; # Are we already processing a comment ? if($incomment) { if($line =~ m,\*/,) { # Get rid of line up to end of comment $line =~ s,^.*\*/, ,; # Line finished the comment $incomment = 0; } else { # Line doesn't finish the comment $line = " "; } } if(! $incomment) { if($line =~ m,/\*,) { # Get rid of line after beginning of comment $line =~ s,/\*.*$, ,; # Line starts the comment $incomment = 1; } } # Get rid of C++ comments too if(! $incomment) { $line =~ s,//.*$, ,; } return ($line, $linenumber); } ############################################################################### #/*@@ # @routine Get next line # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Read a line from the current file descriptor. # Dealing with continuation lines. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub GetNextLine { local *INFILE = $_[0]; my $linenumber = $_[1]; my $line = ; $linenumber++; # Deal with continuation lines while($line =~ m/\\\n$/) { chop($line); chop($line); $line .= ; $linenumber++; } chop($line); return ($line, $linenumber); } ############################################################################### #/*@@ # @routine ProcessIf # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Parse an #if statement and return true or false. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub ProcessIf { my($line,$filename,$linenumber) = @_; my $retval = 0; if($debug) { print "if requested on $line\n"; } if($line =~ m/^def\s+(\S+)/) { $retval = defined($defines{$1}) ? 1 : 0; } elsif($line =~ m/^ndef\s+(\S+)/) { $retval = defined($defines{$1}) ? 0 : 1; } elsif($line =~ m/^\s+(defined\s+)?(\S+)\s*$/) { my $val = $2; if($val =~ m/^\d+$/) { $retval = $val; } else { # print STDERR "#if called on non-digit and non-define $val at $filename\::$linenumber !\n"; $retval = defined($defines{$val}) ? ($defines{$val} ? 1 : 0) : 0; } } else { print STDERR "#if can currently do #ifdef and #ifndef, sorry ! (At $filename\::$linenumber.)\n"; $retval = 0; } if($debug) { print "retval is $retval\n"; } return $retval; } ############################################################################### #/*@@ # @routine Define # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Define a macro. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub Define { my ($arg1,$arg2,$filename,$linenumber) = @_; $arg1 =~ m:^([a-zA-Z_][a-zA-Z0-9_]*)(\(([a-zA-Z0-9_,\s]+)\))?$:; my $defname = $1; my $defargs = $3; my @args = split(/,/, $defargs); # Remove any whitespace around an argument name my $arg; foreach $arg (@args) { $arg =~ /\s*(.*)\s*/; $arg = $1; if ($debug) { print "Arg is '$arg'\n"; } } if($debug) { print "Args are '$arg1' and '$arg2'\n"; print "Defining '$defname' with '$defargs'\n"; } if($defines{$defname}) { # print STDERR "Redefining $defname at $filename:$linenumber\n"; } # Translate argument names just once at original definition. my @transargs = (); for(my $arg = 0; $arg < @args; $arg++) { $transargs[$arg] = "__^CCTK_INTERNAL${arg}__"; } my $newbody = &ArgumentSubstitute($arg2, scalar(@args), @args, @transargs); $defines{$defname}{"ARGS"} = \@transargs; $defines{$defname}{"BODY"} = $newbody; } ############################################################################### #/*@@ # @routine Undefine # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Undefine a macro. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub UnDefine { my ($def,$filename,$linenumber) = @_; delete $defines{$def}; } ############################################################################### #/*@@ # @routine ExpandMacro # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Expand a macro recursively. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub ExpandMacro { my ($macro, $args, $filename, $linenumber) = @_; my $retcode = 0; my @arguments; # SplitArgs returns one (empty) argument for macros without arguments, # because it cannot distinguish between no arguments and one empty argument. if (@{$defines{$macro}{"ARGS"}} == 0) { @arguments = (); } else { @arguments = &SplitArgs($args); } # Test for empty arguments my $have_empty_args = 0; foreach my $arg (@arguments) { if ($arg eq '') { $have_empty_args = 1; } } if ($have_empty_args) { if ($empty_args_error) { print STDERR "$filename:$linenumber: Error: Empty macro argument\n"; ++ $errorcount; } elsif ($empty_args_warning) { print STDERR "$filename:$linenumber: Warning: Empty macro argument\n"; } } my $outstring = $defines{$macro}{"BODY"}; if($macro eq "__FILE__") { $outstring = "\"$filename\""; } elsif($macro eq "__LINE__") { $outstring = "$linenumber"; } if(@arguments != @{$defines{$macro}{"ARGS"}}) { my $expected = @{$defines{$macro}{"ARGS"}}; my $got = @arguments; print STDERR "Error expanding macro '$macro' at $filename:$linenumber\n"; print STDERR " Expected $expected arguments\n"; print STDERR " Got $got arguments\n"; $outstring = $macro; $retcode--; } else { my @prescanned_args; # Argument prescan for(my $arg = 0; $arg < @arguments; $arg++) { my $retval; ($prescanned_args[$arg],$retval) = &ParseAndExpand($arguments[$arg], $filename, $linenumber); $retcode += $retval; } # Argument substitution for(my $arg = 0; $arg < @arguments; $arg++) { if($debug) { print "Outstring is '$outstring'\n"; print "Arg $arg: '$defines{$macro}{\"ARGS\"}[$arg]', '$arguments[$arg]', '$prescanned_args[$arg]'\n"; } my $tobesubsted = quotemeta($defines{$macro}{"ARGS"}[$arg]); # Concatenation takes non-prescanned argument $outstring =~ s/##\s*$tobesubsted\b/##$arguments[$arg]/g; # Stringification takes non-prescanned argument and stringifies it $outstring =~ s/#\s*$tobesubsted\b/\"$arguments[$arg]\"/g; $outstring =~ s/\b$tobesubsted\b/$prescanned_args[$arg]/g; } # Now recurse ($outstring,$retval) = &ParseAndExpand($outstring, $filename, $linenumber); $retcode += $retval; # Final Concatenation $outstring =~ s/\s*##\s*//g; # Now get rid of repeated "" $outstring =~ s/\\\"/__CCTK_STRINGPROTECT__/g; $outstring =~ s/\"\"//g; $outstring =~ s/__CCTK_STRINGPROTECT__/\\\"/g; } return ($outstring,$retcode); } ############################################################################### #/*@@ # @routine SplitArgs # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Split the arguments given to a macro into an array. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub SplitArgs { my ($args) = @_; my @outargs; # Split the input into individual chars my @splitargs = split(//, $args); my $nestlevel = 0; my @thistoken = (); my $insstring = 0; my $indstring = 0; for(my $pos = 0; $pos < @splitargs; $pos++) { if($splitargs[$pos] eq '\'' || $splitargs[$pos] eq '"' || $insstring == 1 || $indstring == 1) { # Just pass the token through if in a string. if($splitargs[$pos] eq '\'') { if($pos == 0 || ($pos > 0 && $splitargs[$pos-1] ne '\\')) { $insstring = 1 - $insstring; } } if($splitargs[$pos] eq '"') { if($pos == 0 || ($pos > 0 && $splitargs[$pos-1] ne '\\')) { $indstring = 1 - $indstring; } } } elsif($splitargs[$pos] eq "(") { # Increase nesting level $nestlevel++; } elsif($splitargs[$pos] eq ")") { # Decrease nesting level $nestlevel--; } elsif($splitargs[$pos] eq "," && $nestlevel == 0) { # At top level, and not in a string, so must be end of this arg. push(@outargs, join("",@thistoken)); @thistoken = (); next; } push(@thistoken, $splitargs[$pos]); } # Push the remaining token push(@outargs, join("",@thistoken)); return @outargs; } ############################################################################### #/*@@ # @routine ParseAndExpand # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Parse a string and expand any macros in it. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub ParseAndExpand { my ($line,$filename, $linenumber) = @_; # Split the line into individual characters. my @splitline = split(//, $line); my @outline = (); my $retcode = 0; my $insstring = 0; my $indstring = 0; for(my $pos = 0 ; $pos < @splitline; $pos++) { # Pass through anything we're not interested in and anything in a string. if($splitline[$pos] !~ m/[A-Za-z_]/ || $insstring == 1 || $indstring == 1) { if($splitline[$pos] eq '\'') { if($pos == 0 || ($pos > 0 && $splitline[$pos-1] ne '\\')) { $insstring = 1 - $insstring; } } if($splitline[$pos] eq '"') { if($pos == 0 || ($pos > 0 && $splitline[$pos-1] ne '\\')) { $indstring = 1 - $indstring; } } push(@outline, $splitline[$pos]); next; } # Ok, should be at the beginning of a token my $token = $splitline[$pos]; while($pos+1 < @splitline && $splitline[$pos+1] =~ m:[A-Za-z0-9_]:) { $pos++; $token .= $splitline[$pos]; } # Is this token a macro ? if($defines{$token}) { if ($debug) { print "The macro is '$token'\n"; } my $arg = ""; if(@{$defines{$token}{"ARGS"}} > 0 && $pos+1 < @splitline) { # Eat up whitepace between token and arguments for(my $newpos=$pos+1; $newpos < @splitline; $newpos++) { next if($splitline[$newpos] =~ m/\s/); if($splitline[$newpos] eq "(") { $pos = $newpos-1; last; } else { last; } } # Find any arguments if($splitline[$pos+1] eq "(") { my $insstring = 0; my $indstring = 0; $pos++; my $depth = 1; $pos++; while($pos < @splitline && $depth > 0) { if($splitline[$pos] eq '\'' && ! $indstring) { $insstring = 1 - $insstring; } if($splitline[$pos] eq '"'&& ! $insstring) { $indstring = 1 - $indstring; } if($splitline[$pos] eq "(" && ! $insstring && ! $indstring) { $depth++; } elsif($splitline[$pos] eq ")" && ! $insstring && ! $indstring) { $depth--; } if($depth > 0) { $arg .= $splitline[$pos]; $pos++; } } } } if($debug) { print "Token is '$token', arguments are '$arg'\n"; } # Expand the macro my($expanded,$retval) = &ExpandMacro($token,$arg,$filename,$linenumber); $retcode += $retval; if($debug) { print "Expanded version is '$expanded'\n"; } #Put the final expanded version into output. push(@outline, $expanded); } else { push(@outline, $token); } } return (join("",@outline),$retcode); } ############################################################################### #/*@@ # @routine ArgumentSubstitute # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Substitute all non-string-enclosed arguments with replacement values. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub ArgumentSubstitute { my ($body, $nargs, @args) = @_; my @splitbody = split(//,$body); my @outbody = (); my $insstring = 0; my $indstring = 0; for(my $pos = 0 ; $pos < @splitbody; $pos++) { # Just pass through all non-tokens and all tokens in a string. if($splitbody[$pos] !~ m/[A-Za-z_]/ || $insstring == 1 || $indstring == 1) { if($splitbody[$pos] eq '\'') { if($pos == 0 || ($pos > 0 && $splitbody[$pos-1] ne '\\')) { $insstring = 1 - $insstring; } } if($splitbody[$pos] eq '"') { if($pos == 0 || ($pos > 0 && $splitbody[$pos-1] ne '\\')) { $indstring = 1 - $indstring; } } push(@outbody, $splitbody[$pos]); next; } # Ok, should be at the beginning of a token my $token = $splitbody[$pos]; while($pos+1 < @splitbody && $splitbody[$pos+1] =~ m:[A-Za-z0-9_]:) { $pos++; $token .= $splitbody[$pos]; } if($debug) { print "Token is '$token'\n"; } for(my $arg = 0; $arg < $nargs; $arg++) { if($token eq "$args[$arg]") { $token = $args[$arg+$nargs]; last; } } push(@outbody, $token); } return join("", @outbody); } ############################################################################### #/*@@ # @routine Print Defines # @date Mon Nov 19 23:51:03 2001 # @author Tom Goodale # @desc # Print all the macros in the symbol table. # @enddesc # @calls # @calledby # @history # # @endhistory # #@@*/ sub PrintDefines { my $def; foreach $def (sort keys %defines) { print "Macro '$def'\n"; print @{$defines{$def}{"ARGS"}} . " arguments\n"; if (@{$defines{$def}{"ARGS"}}) { for (my $arg = 0 ; $arg < @{$defines{$def}{"ARGS"}}; $arg++) { print "$arg: $defines{$def}{\"ARGS\"}[$arg]\n"; } } print "Body '$defines{$def}{\"BODY\"}'\n"; } }