+#
+# Extract documentation from a single function.
+#
+sub process_function {
+
+ my ($file, $file_path, $docs) = @_;
+
+ my $PARAMETER_SECTION = 0;
+ my $BODY_SECTION = 1;
+ my $RETURN_SECTION = 2;
+ my $section = $PARAMETER_SECTION;
+
+ my $name = do {
+ $_ = <$file>;
+ chomp;
+ s/^ \* //;
+ s/:$//;
+ $_
+ };
+
+ # Ignore irrelevant functions, and those with the wrong doc format.
+ return if $name !~ /^mono_\w+$/;
+
+ my $deprecated;
+ my @parameters = ();
+ my $body = '';
+ my $returns = '';
+ my $prototype = '';
+
+ while (<$file>) {
+
+ # We've reached the last line in the documentation block.
+ if (/^ \*\*?\//) {
+
+ # Grab function prototype.
+ while (<$file>) {
+ $prototype .= $_;
+ last if /\{/;
+ }
+
+ # Clean up prototype.
+ $prototype = do {
+ $_ = $prototype;
+ # Strip braces and trailing whitespace.
+ s/{//;
+ s/ +$//;
+ # Turn "Type * xxx" into "Type* xxx"
+ s/^(\w+)\W+\*/$1*/;
+ $_;
+ };
+
+ # Process formatting within sections.
+ for my $parameter (@parameters) {
+ process_formatting(\$parameter->{description}, $file_path, $.);
+ }
+ process_formatting(\$returns, $file_path, $.);
+ process_formatting(\$body, $file_path, $.);
+ if (defined($deprecated)) {
+ process_formatting(\$deprecated, $file_path, $.);
+ }
+ $body =~ s/\n/ /g;
+
+ if (exists($docs->{body}->{$name})) {
+ my $origin = $docs->{origin}->{$name};
+ if ($WARNINGS) {
+ warn
+ "$file_path:$.: Redundant documentation for $name\n",
+ "$origin->{file}:$origin->{line}: Previously defined here\n";
+ }
+ }
+ $docs->{origin}->{$name} = { file => $file_path, line => $. };
+ $docs->{body}->{$name} = $body;
+ $docs->{parameters}->{$name} = \@parameters;
+ $docs->{deprecated}->{$name} = $deprecated if defined $deprecated;
+ $docs->{return}->{$name} = $returns;
+ $docs->{prototype}->{$name} = $prototype;
+ last;
+
+ }
+
+ # Strip newlines and asterisk prefix.
+ chomp;
+ s/^ +\*//;
+
+ # Replace blank lines with paragraph breaks.
+ $_ = '<p>' if /^\s*$/;
+
+ if ($section == $PARAMETER_SECTION) {
+ if (/\s*\\param +(\w+)(.*)/) {
+ # print "$file_path:$.: warning: Got parameter $1\n";
+ push @parameters, { name => $1, description => $2 };
+ } elsif (/\s*\\deprecated(.*)/) {
+ # print "$file_path:$.: warning: Got deprecated annotation\n";
+ $deprecated = $1;
+ } elsif (/\s*(\w+):(.*)/) {
+ if ($1 eq 'deprecated') {
+ warn "$file_path:$.: Old-style monodoc notation 'deprecated:' used\n"
+ if $WARNINGS;
+ $deprecated = $2;
+ } else {
+ warn "$file_path:$.: Old-style monodoc notation 'param:' used\n"
+ if $WARNINGS;
+ push @parameters, { name => $1, description => $2 };
+ }
+ } else {
+ # $body = "\t$_\n";
+ $section = $BODY_SECTION;
+ redo;
+ }
+ } elsif ($section == $BODY_SECTION) {
+ if (s/(Returns?:\s*|\\returns?\s*)//) {
+ $returns = "\t$_\n";
+ $section = $RETURN_SECTION;
+ } else {
+ $body .= "\n\t$_";
+ }
+ } elsif ($section == $RETURN_SECTION) {
+ $returns .= "\n\t$_";
+ } else {
+ die "Invalid section $section\n";
+ }
+ }
+}
+
+#
+# Substitute formatting within documentation text.
+#
+sub process_formatting {
+ my ($content, $file_path, $current_line) = @_;
+ $_ = $$content;
+
+ # Constants
+ s{NULL}{<code>NULL</code>}g;
+ s{TRUE}{<code>TRUE</code>}g;
+ s{FALSE}{<code>FALSE</code>}g;
+
+ # Parameters
+ warn "$file_path:$current_line: Old-style monodoc notation '\@param' used\n"
+ if s{@(\w+)}{<i>$1</i>}g && $WARNINGS;
+ s{\\p +(\w+)}{<i>$1</i>}g;
+
+ # Code
+ warn "$file_path:$current_line: Old-style monodoc notation '#code' used\n"
+ if s{#(\w+)}{<code>$1</code>}g && $WARNINGS;
+ warn "$file_path:$current_line: Old-style monodoc notation '`code`' used\n"
+ if s{\`((?!api:)[:.\w\*]+)\`}{<code>$1</code>}g && $WARNINGS;
+ s{\\c +(\S+(?<![.,:;]))}{<code>$1</code>}g;
+
+ $$content = $_;