1#!/usr/bin/env perl
2
3=head1 NAME
4
5fixcomments.pl
6
7=head1 SYNOPSIS
8
9fixcomments.pl [options] [infile]
10
11=head1 OPTIONS
12
13=over 4
14
15=item B<infile>
16
17Input file to process (uses stdin if not specified)
18
19=item B<--help|-h>
20
21A little help
22
23=item B<--verbose|-v>
24
25Output debug messages (to stderr), repeat for even more output
26
27=back
28
29=head1 DESCRIPTION
30
31This script will read a Fortran file and attempt to add doxify comments
32to both FUNCTIONs and SUBROUTINEs.
33
34=cut
35
36use strict;
37use warnings;
38use FindBin;
39use lib "$FindBin::RealBin/lib";
40use Pod::Usage qw(pod2usage);
41use Getopt::Long;
42
43my $verbose = 0;
44my $help = 0;
45
46GetOptions(
47    'verbose+' => \$verbose,
48    'help|?' => \$help) or pod2usage(2);
49pod2usage(1) if $help;
50
51sub print_debug {
52    print(STDERR "DEBUG: @_\n") if ($verbose > 0);
53}
54
55# Regular expressions for matching
56my $DOXYGEN_HEADER = "^!>";
57
58# The empty string
59my $EMPTY = q{};
60
61# Toggle variables to keep track of which doxygen item is being processed
62# in the current header.
63my ($hasParam,
64    $hasBrief,
65    $hasPar,
66    $hasDate,
67    $hasVersion,
68    $hasAuthor,
69    $hasNote,
70    $hasRetVal,
71    $hasReturn,
72    $hasRandom,
73    $hasRemainder);
74
75# Variables with s at the end contains the actual text read in from
76# existing doxygen header
77my (%params,
78    $briefs,
79    $dates,
80    $versions,
81    $pars,
82    $authors,
83    $notes,
84    $retVals,
85    $returns,
86    $randoms,
87    $remainders);
88
89# If currently in INTERFACE block
90my $insideInterface = 0;
91
92# Whether the SUBROUTINE/FUNCTION definition contains an & line continuation
93my $hasAmpersand = 0;
94
95# Whether when processing subroutine definition we are in brackets
96my $inParentheses = 0;
97
98# Whether the procedure definition contains the RETURN value
99my $hasReturnAsArg = 0;
100
101# Keeps track of which parameters in the current procedure have
102# a matching doxygen header
103my %matched;
104
105# Buffer for lines
106my $buffer = $EMPTY;
107
108# The old doxygen header as read from file
109my $oldheader = $EMPTY;
110
111# Any unprocessed lines
112my $leftoverlines = $EMPTY;
113
114# The name of the procedure being processed
115my $procedureName = $EMPTY;
116
117# Whether the procedure is a function
118my $isFunction;
119
120initVariables();
121
122# While there are still lines to read in our INPUT file
123while (<>) {
124    # Get the line we've just read in
125    my $currline = $_;
126
127    # If an existing doxygen header is encountered then process
128    if (matchDoxygenHeaderDefinition($currline)) {
129        print_debug("Matched doxygen header");
130        $currline = processDoxygenHeader($currline);
131    }
132
133    # Are we inside an interface block?
134    # If so we don't want to add any comments here
135    if ( $currline =~ m/^\s*INTERFACE\s*\n/xms ) {
136        print_debug("Start of INTERFACE encountered");
137        $insideInterface = 1;
138    }
139    if ( $currline =~ m/^\s*END\s+INTERFACE\s*\n/xms ) {
140        print_debug("End of INTERFACE encountered");
141        $insideInterface = 0;
142    }
143
144    # Look for procedure (SUBROUTINE/FUNCTION definitions)
145    # These can be the initial definition line, or a continuation line
146    # We don't add comments to code inside an interface block
147    if ((matchSubroutineDefinition($currline) || $hasAmpersand)
148        && !($insideInterface)) {
149        print_debug("Matched subroutine definition");
150        processSubroutineDefinition($currline);
151    } else {
152        print_debug("Subroutine definition not matched");
153        if ($oldheader eq $EMPTY) {
154            # No header remaining so just print out line as read
155            print_debug("Empty old header, writing out line:");
156            print_debug($currline);
157            print $currline;
158        } else {
159            # Header has been processed, need to do something with remaining lines
160            print_debug("Non-empty old header for line:");
161            print_debug($currline);
162            if (($currline =~ m/^\s*$/xms) ||
163                ($currline =~ m/\#if/xms)) {
164                # Blank line or #if line, so store for printing later
165                print_debug("Empty line or #if, storing for later");
166                $leftoverlines = $leftoverlines . $currline;
167            } elsif ($currline =~ m/^\s*!\s*/xms) {
168                # Have a comment so add to header
169                print_debug("Inline comment, adding to header");
170                $oldheader = $oldheader . $currline;
171            } else {
172                # If it was a MODULE or TYPE header we still need to write
173                # it back out to file.
174                print_debug("Writing existing non FUNCTION/SUBROUTINE header to file");
175                print $oldheader;
176                print $leftoverlines;
177                print $currline;
178                # Reset variables after output
179                initVariables();
180            }
181        }
182    } # End of the if (($currline is SUBROUTINE or FUNCTION ) block
183} # End of main while loop
184
185# Perhaps do a second pass to remove any double occurrences of !> *** type lines
186
187sub initToggles {
188    $hasParam = 0;
189    $hasBrief = 0;
190    $hasPar = 0;
191    $hasDate = 0;
192    $hasVersion = 0;
193    $hasAuthor = 0;
194    $hasNote = 0;
195    $hasRetVal = 0;
196    $hasRandom = 0;
197    $hasRemainder = 0;
198    return;
199}
200
201sub initVariables {
202    initToggles();
203    undef %params;
204    undef %matched;
205    $briefs = $EMPTY;
206    $dates = $EMPTY;
207    $versions = $EMPTY;
208    $pars = $EMPTY;
209    $authors = $EMPTY;
210    $notes = $EMPTY;
211    $retVals = $EMPTY;
212    $returns = $EMPTY;
213    $randoms = $EMPTY;
214    $remainders = $EMPTY;
215    $buffer = $EMPTY;
216    $oldheader = $EMPTY;
217    $leftoverlines = $EMPTY;
218    $procedureName = $EMPTY;
219    $isFunction = 0;
220    $inParentheses = 0;
221    return;
222}
223
224# Look for a doxygen header definition in an input line.
225sub matchDoxygenHeaderDefinition {
226    my ($lineToProcess) = @_;
227
228    print_debug("Trying to match doxygen header $DOXYGEN_HEADER against line");
229    print_debug($lineToProcess);
230
231    my $match = 0;
232
233    if ($lineToProcess =~ m/$DOXYGEN_HEADER/xms) {
234        $match = 1;
235    }
236
237    print_debug("Doxygen header match value: $match");
238    return $match;
239}
240
241# Look for SUBROUTINE or FUNCTION definitions.
242sub matchSubroutineDefinition {
243    my ($lineToProcess) = @_;
244
245    print_debug("Trying to match subroutine definition against line:");
246    print_debug($lineToProcess);
247
248    # Immediately discount lines with comments at the start
249    my $commentPattern = '^\!';
250    if ($lineToProcess =~ m/$commentPattern/xms) {
251        print_debug("Matched comment");
252        return 0;
253    }
254    # Assume that lines contain SUBROUTINE or FUNCTION followed by space
255    # and then the name of the procedure which may have a space before the (
256    my $patternProcWithBrackets = '(SUBROUTINE|FUNCTION)'    # Subroutine or function
257                                 .'\s+'                      # followed by one or more whitespace characters
258                                 .'(\w|\[|\])+'              # followed by one or more of (word or [ or ])
259                                 .'\s*'                      # followed by zero or more whitespace characters
260                                 .'\(';                      # followed by open bracket.
261
262    # Need to allow for SUBROUTINE without any arguments or brackets too
263    my $patternSubNoBracketsAtStartOfLine = '^\s*'           # Start with zero or more whitespace
264                                           .'SUBROUTINE'     # then subroutine
265                                           .'\s*'            # then zero or more whitespace
266                                           .'(\w|\[|\])+'    # then one or more of (word or [ or ])
267                                           .'\s*.*';         # then zero or more whitespace
268
269    # We also protect against adding comments to commented out SUBROUTINE/FUNCTION calls
270    my $patternCommentedOut = '!\s*(SUBROUTINE|FUNCTION)';
271
272    # Protect against definitions inside quotes
273    my $patternInQuotes = '"\s*(SUBROUTINE|FUNCTION)';
274
275    my $match1 = 0;
276    my $match2 = 0;
277    my $match3 = 0;
278    my $match4 = 0;
279
280    if ($lineToProcess =~ m/$patternProcWithBrackets/xms) {
281        $match1 = 1;
282    }
283    if ($lineToProcess =~ m/$patternSubNoBracketsAtStartOfLine/xms) {
284        $match2 = 1;
285    }
286    if ($lineToProcess =~ m/$patternCommentedOut/xms) {
287        $match3 = 1;
288    }
289    if ($lineToProcess =~ m/$patternInQuotes/xms) {
290        $match4 = 1;
291    }
292
293    my $match = (($match1 || $match2) && !($match3) && !($match4));
294    print_debug("Subroutine definition match value: $match");
295    return $match
296}
297
298sub processDoxygenHeader {
299
300    my ($currline) = @_;
301
302    print_debug("Processing Doxygen Header");
303
304    # Each time we find a header we need to reset toggles and their data
305    initVariables();
306    my $paramName = $EMPTY;
307
308    # Start of do-while over match on ($currline =~ m/^\s*!/i)
309    do {
310        print_debug("Processing header line:");
311        print_debug($currline);
312        # Keep the headers safe, may need them! We use the oldheader variable to
313        # keep the complete headers for MODULE and TYPE intact such that we
314        # can just dump them straight back out without making any changes.
315        $oldheader = $oldheader . $currline;
316        # Pick up parameters - matches the word param separated by spaces
317        if (($currline =~ m/!>\s\\param\s+(\S+)\s*/xms) ||
318            ($currline =~ m/!>\s\\param\[.*\]\s+(\S+)\s*/xms)) {
319
320            # Get the param name
321            $paramName = $1;
322
323            print_debug("Got header for parameter $paramName");
324
325            # Cover the case where two arguments have the same name
326            if (exists $params{$paramName}) {
327                $paramName = $paramName . "new";
328            }
329            # Store the param into a hash indexed by its name
330            $params{$paramName} = $currline;
331            # Set matched for this param to zero as we don't
332            # yet have a match in the argument list
333            $matched{$paramName} = 0;
334            initToggles();
335            $hasParam = 1;
336        } elsif ($currline =~ m/!>\s\\brief\s*/xms) {
337            print_debug("Got briefs header");
338            $briefs = $briefs . $currline;
339            initToggles();
340            $hasBrief = 1;
341        } elsif ($currline =~ m/!>\s\\date*/xms) {
342            print_debug("Got dates header");
343            $dates = $dates . $currline;
344            initToggles();
345            $hasDate = 1;
346        } elsif ($currline =~ m/!>\s\\version\s*/xms) {
347            print_debug("Got version header");
348            $versions = $versions . $currline;
349            initToggles();
350            $hasVersion = 1;
351        } elsif ($currline =~ m/!>\s\\par\s*/xms) {
352            print_debug("Got par header");
353            $pars = $pars . $currline;
354            initToggles();
355            $hasPar = 1;
356        } elsif ($currline =~ m/!>\s\\author\s*/xms) {
357            print_debug("Got author header");
358            $authors = $authors . $currline;
359            initToggles();
360            $hasAuthor= 1;
361        } elsif ($currline =~ m/!>\s\\note\s*/xms) {
362            print_debug("Got note header");
363            $notes = $notes . $currline;
364            initToggles();
365            $hasNote = 1;
366        } elsif ($currline =~ m/!>\s\\retval\s*/xms) {
367            print_debug("Got retval header");
368            $retVals = $retVals . $currline;
369            initToggles();
370            $hasRetVal = 1;
371        } elsif ($currline =~ m/!>\s\\returns\s*/xms) {
372            print_debug("Got return header");
373            $returns = $returns . $currline;
374            initToggles();
375            $hasReturn = 1;
376        } elsif ($currline =~ m/!>\s\\return\s*/xms) {
377            print_debug("Got return header");
378            $returns = $returns . $currline;
379            initToggles();
380            $hasReturn = 1;
381        } elsif ($currline =~ m/!>\s\\\S+/xms) {
382            # Randoms contains anything else that looks
383            # like a DOXYGEN header. with a \whatever
384            # Must check to see if line has already been commented.
385            # We also avoid commenting a blank \param line
386            print_debug("Got random header");
387            if (($currline !~ m/UNKNOWN_DOXYGEN_COMMENT/xms) &&
388                ($currline !~ m/UNKNOWN_COMMENT/xms) &&
389                ($currline !~ m/!>\s*\\param\s*\n/xms)) {
390                $randoms = $randoms . $currline;
391                chomp($randoms);
392                # Add on text for output
393                $randoms = $randoms . " UNKNOWN_DOXYGEN_COMMENT\n";
394            } else {
395                # Otherwise just add to randoms
396                $randoms = $randoms . $currline;
397            }
398            initToggles();
399            $hasRandom = 1;
400        } elsif ($currline =~ m/^!>\s*/xms) {
401            # Handle multi line entries. Append what you find onto the
402            # previous one read in until you get to another \
403            # The \brief, \param, \par, \author, random and remainder
404            # entries can all be multi-line.
405            # The \version and \date entries should be single line and
406            # thus we don't have an elsif for them.
407            print_debug("Got line continuation");
408            if ($hasParam) {
409                $params{$paramName} = $params{$paramName} . $currline;
410            } elsif ($hasBrief) {
411                $briefs = $briefs . $currline;
412            } elsif ($hasPar) {
413                $pars = $pars . $currline;
414            } elsif ($hasAuthor) {
415                $authors = $authors . $currline;
416            } elsif ($hasNote){
417                $notes = $notes . $currline;
418            } elsif ($hasRetVal) {
419                $retVals = $retVals . $currline;
420            } elsif ($hasReturn) {
421                $returns = $returns . $currline;
422            } elsif ($hasRandom) {
423                # Must check to see if line has already been commented
424                if (($currline !~ m/UNKNOWN_DOXYGEN_COMMENT/xms) &&
425                    ($currline !~ m/UNKNOWN_COMMENT/xms)) {
426                    $randoms = $randoms . $currline;
427                    chomp($randoms);
428                    # Add on text for output
429                    $randoms = $randoms . " UNKNOWN_DOXYGEN_COMMENT\n";
430                } else {
431                    $randoms = $randoms . $currline;
432                }
433            } else {
434                # Get any header lines beginning with "!> some text but
435                # no \ thus not in DOXYGEN format
436                $hasRemainder = 1;
437                # Must check to see if the line has already been
438                # commented and also that it's not empty
439                if (($currline !~ m/UNKNOWN_COMMENT/xms) &&
440                    ($currline !~ m/!>\s*\n/xms)) {
441                    # Add on text for output
442                    $currline =~ s/!>/!> \\note UNKNOWN_COMMENT /gx;
443                    $remainders = $remainders . $currline;
444                } else {
445                    $remainders = $remainders . $currline;
446                }
447            }
448        } elsif ($currline !~ m/^!\s*\*/xms) {
449            # Any other header line that's not "***..." or a Doxygen
450            # comment, ie "! some comment or other"
451            print_debug("Got non-doxygen line");
452            if ($hasBrief) {
453                # Put comment in brief, replacing ! with !>
454                $currline =~ s/!/!>/xms;
455                $briefs = $briefs . $currline;
456            } else {
457                $currline =~ s/^\s+//;
458                # Must check to see if line has already been commented
459                if ($currline !~ m/UNKNOWN_COMMENT/xms) {
460                    # Add on text for output, changing ! for !>
461                    $currline =~ s/!/!> \\note UNKNOWN_COMMENT /gx;
462                    $remainders = $remainders . $currline;
463                } else {
464                    $remainders = $remainders . $currline;
465                }
466            }
467        }
468        # Get the next line in the header block
469        $currline = <>;
470    } while ($currline =~ m/^\s*!/xms); # Rule as to when you have finished a header. Currently Anything beginning with !
471    return $currline;
472} # End of header processing subroutine
473
474sub processSubroutineDefinition {
475
476    my ($currline) = @_;
477
478    print_debug("Processing Subroutine line:");
479    print_debug($currline);
480    # functionLine will contain the procedure definition with any
481    # unrequired text stripped off, $currline will still contain the actual code
482    my $functionLine = $EMPTY;
483    if (!($hasAmpersand)) {
484        # Remove anything preceding the SUBROUTINE or FUNCTION definition
485        # e.g. RECURSIVE, REAL, INTEGER, (KIND=dp), ELEMENTAL etc etc.
486        $currline =~ /((\bFUNCTION\b|\bSUBROUTINE\b).+)/x;
487        # $1 contains whatever remains after removing anything before the
488        # word SUBROUTINE or FUNCTION
489        $functionLine = $1;
490    } else {
491        $functionLine = $currline;
492    }
493    # Remove BIND(*) from the definition
494    my $tmp = $functionLine;
495    $tmp =~ s/\bBIND\b\(\w+\W*\w*\W*\w*\W*\)//x;
496    $functionLine = $tmp;
497
498    # Strip the newline char from the end
499    chomp($functionLine);
500
501    $hasAmpersand = 0;
502    $hasReturnAsArg = 0;
503    my $lelement = $EMPTY;
504
505    # Split the subroutine or function definition by space or comma
506    my @string = split(/([,\(\)\s+]+)/x, $functionLine);
507
508    while (my ($idx, $p) = each @string) {
509        defined $p or next;  # continue with the next item if $p is undefined
510
511        $p =~ s/^\s+|\s+$//gx;
512
513        $p ne $EMPTY or next;  # continue with the next item if empty
514        $p ne "," or next;  # or simply the ','
515
516        print_debug("Processing item $p");
517        if ($p eq "&") {
518            # If we encounter an & as the last element in this line it starts a line continuation,
519            # otherwise it is the continuation point of a previous one and we can ignore it
520            $idx == $#string or next;
521            print_debug("Got & line continuation");
522            $hasAmpersand = 1;
523            # Buffer the line details as we can't print out yet
524            $buffer = "$buffer$currline";
525        } elsif ($p eq "(") {
526            print_debug("Entered parentheses");
527            $inParentheses = 1;
528        } elsif ($p eq ")") {
529            print_debug("Left parentheses");
530            $inParentheses = 0;
531        } elsif ($p eq ",") {
532            print_debug("Comma");
533        } else {
534            if ($inParentheses) {
535                print_debug("In parentheses, parameter: $p, previous parameter: $lelement");
536                # Must have either a parameter of a function return value
537                if (($lelement =~ m/RESULT/ixms) && ($hasReturnAsArg)) {
538                    print_debug("Got return parameter $p");
539                    if ($returns eq $EMPTY) {
540                        # If the previous element was RESULT outside of
541                        # parantheses then this element must be whatever
542                        # gets returned by the procedure.
543                        # if no return data is available print out the ...
544                        # to header
545                        # Only stored for now so it is always printed
546                        # in the same place (after the \params)
547                        $returns = "!> \\return ...\n";
548                    }
549                } else {
550                    # Must be parameter
551                    # Update the matched hash table so that all arguments
552                    # in subroutine/function header are set as true
553                    print_debug("Processing parameter $p");
554                    $matched{$p} = 1;
555                    if (!(exists($params{$p})) || !(defined($params{$p})) || ($params{$p} eq $EMPTY)) {
556                        # If the entry for this parameter is missing we use
557                        # the standard text for a missing entry
558                        print_debug("Missing entry for parameter $p, creating blank");
559                        print "!> \\param $p ...\n";
560                    } else {
561                        print_debug("Using existing entry for parameter $p");
562                        if ($params{$p} !~ m/\\param\s*(\w+|\[.*\]\s+\w+)\s*\n/xms ) {
563                            # Entry must contain some text after the parameter name
564                            print_debug("Using entry unchanged");
565                            print $params{$p};
566                        } else {
567                            if ($params{$p} =~ m/!>\s*\n$/x) {
568                                # We need to guard against \param entries which have
569                                # no text but have a blank line "!>" line appended
570                                # Need to split this parameter into it's individual lines
571                                my @tmpString = split(/\n/x, $params{$p});
572                                # Find out how many lines we have in this entry
573                                my $tmpLen = scalar(@tmpString);
574                                # Append on the ... to the first line
575                                $tmpString[0] = $tmpString[0] . " ...";
576                                for (my $i = 0; $i < $tmpLen; $i++) {
577                                    # Re-add the carriage return to each line
578                                    print "$tmpString[$i]\n";
579                                }
580                            } else {
581                                chomp($params{$p});
582                                print $params{$p}," ...\n";
583                            }
584                        }
585                    }
586                }
587            } else {
588                print_debug("Processing element not in parentheses: $p");
589                if ($lelement =~ m/(SUBROUTINE|FUNCTION)/ixms) {
590                    # Previous element is FUNCTION or SUBROUTINE so this must be name
591                    $procedureName = $p;
592                    print_debug("Got procedure name $procedureName");
593                    if ($lelement =~ m/FUNCTION/ixms) {
594                        $isFunction = 1;
595                    }
596                    if (($briefs eq $EMPTY) or ($briefs eq "!> \\brief\n")) {
597                        # \brief does not exist or is present but empty - add text
598                        print "! **************************************************************************************************\n";
599                        print "!> \\brief ...\n";
600                    } else {
601                        # \brief exists and contains text
602                        print $briefs;
603                    }
604                } elsif ($p =~ m/RESULT/ixms) {
605                    # Check to see if parameter is RESULT for a FUNCTION
606                    print_debug("Need to get function result parameter");
607                    $hasReturnAsArg = 1;
608                }
609            }
610            $lelement = $p; # Take a note of the array element for comparison, ignoring & and ()
611        }
612    } # End of for loop over @string
613
614    # Code to loop over all the elements of matched hash table. If any
615    # remain that were not in the function/subroutine header we want to
616    # make sure we keep this data in the header. It could be a comment
617    # or it could refer to old arguments that no longer exist.
618    # However, we don't want to throw the text away. We only print out
619    # if the argument doesn't match one of the arguments *and* there is
620    # no more of the procedure definition left to read, i.e. hasAmpersand is false
621    foreach my $paramName (sort keys %params) {
622        # We need to sort the keys otherwise the output order of the hash
623        # is given in internal order
624        if (!($matched{$paramName}) && !($hasAmpersand)) {
625            if ($params{$paramName} eq '!> \param '.$paramName." ...\n") {
626                # there was no comment, just drop parameter
627            } elsif ($params{$paramName} !~ m/UNMATCHED_PROCEDURE_ARGUMENT/) {
628                # Must protect against updating an existing comment
629                # Get rid of \n so UNMATCHED* text can be appended on.
630                chomp($params{$paramName});
631                print $params{$paramName} . " UNMATCHED_PROCEDURE_ARGUMENT: please check \n";
632            } else {
633                print $params{$paramName};
634            }
635        }
636    }
637
638    # If after looping through the elements there is no ampersand
639    # we close off the comment and write out the procedure definition
640    if (!($hasAmpersand)) {
641        if ($returns ne $EMPTY) {
642            # Print RESULT value first so that it should come straight after the \param definitions
643            print $returns;
644        } else {
645            # Get return value from function name
646            if ($isFunction) {
647                print "!> \\return ...\n";
648            }
649        }
650        if ($retVals ne $EMPTY) {
651            # Print return values definitions second so that they come after any \return definitions
652            print $retVals;
653        }
654        if (($dates eq $EMPTY) || ($dates eq "!> \\date\n")) {
655            # dates entry empty or exists and contains no text
656###            print "!> \\date MISSING_COMMENT: Unknown\n";  # Use this line if you want to add text to the entry
657            print $dates;
658        } else {
659            print $dates;
660        }
661        if (($pars eq $EMPTY) || ($pars eq "!> \\par History\n")) {
662            # pars entry empty or exists but contains no text
663###            print "!> \\par History\n"; # Use this line if you want to add text to the entry
664###            print "!>     MISSING_COMMENT: Unknown\n"; # Use this line if you want to add text to the entry
665            print $pars;
666        } else {
667            print $pars;
668        }
669        if (($authors eq $EMPTY) || ($authors eq "!> \\author\n")) {
670            # authors empty or exists but contains no text
671###            print "!> \\author MISSING_COMMENT: Unknown\n"; # Use this line if you want to add text to the entry
672            print $authors;
673        } else {
674            print $authors;
675        }
676        if ($versions ne $EMPTY) {
677            print $versions;
678        }
679        if ($randoms ne $EMPTY) {
680            print $randoms;
681        }
682        if ($notes ne $EMPTY){
683            print $notes;
684        }
685        if ($remainders ne $EMPTY) {
686            # Dumps out whatever else remainded in the header (e.g. stuff beginning !> without a \ or stuff beginning with just a !) for the SUBROUTINE/FUNCTION at the end
687            print $remainders;
688        }
689        print "! **************************************************************************************************\n";
690        print "$leftoverlines$buffer$currline";
691        # Reset all the variables after writing out the header
692        initVariables();
693        return;
694    }
695}
696