| #!/usr/bin/perl |
| # Generate the Summary of Library Facilities (summary.texi). |
| |
| # Copyright (C) 2017-2018 Free Software Foundation, Inc. |
| # This file is part of the GNU C Library. |
| # Contributed by Rical Jasan <ricaljasan@pacific.net>, 2017. |
| |
| # The GNU C Library is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU Lesser General Public License |
| # as published by the Free Software Foundation; either version 2.1 of |
| # the License, or (at your option) any later version. |
| |
| # The GNU C Library is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # Lesser General Public License for more details. |
| |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with the GNU C Library; if not, see |
| # <http://www.gnu.org/licenses/>. |
| |
| # Anything declared in a header or defined in a standard should have |
| # its origins annotated using the @standards macro (see macro.texi). |
| # This script checks all such elements in the manual (generally, |
| # @def|item*-commands), ensuring annotations are present and correct. |
| # If any errors are detected, they are all reported at the end and |
| # failure is indicated. |
| |
| use strict; |
| use warnings; |
| use locale; |
| use File::Basename; |
| |
| $| = 1; |
| my $script = basename $0; |
| |
| &help if $ARGV[0] eq "--help"; # Will exit(0). |
| |
| my @texis = @ARGV; |
| |
| # Various regexes. |
| my $nde = qr/^\@node /; |
| my $def = qr/^\@def/; |
| my $itm = qr/^\@item /; |
| my $itms = qr/^\@itemx? /; # Don't match @itemize. |
| my $ann = qr/^\@(def\w+|item)x? /; # Annotatable. |
| my $std = qr/^\@standards\{/; |
| my $stx = qr/^\@standardsx\{/; |
| my $stds = qr/^\@standardsx?\{/; |
| my $strict_std = qr/^\@standards\{([^,]+, )[^,\}]+\}$/; |
| my $strict_stx = qr/^\@standardsx\{([^,]+, ){2}[^,\}]+\}$/; |
| my $lcon = qr/([vf]?table|itemize|enumerate)/; |
| my $list = qr/^\@${lcon}/; |
| my $endl = qr/^\@end ${lcon}/; |
| my $ign = qr/^\@ignore/; |
| my $eig = qr/^\@end ignore/; |
| |
| # Global scope. |
| my $node; |
| our $texi; |
| my $input; |
| my %entries; |
| my %errors; |
| |
| for $texi (@texis) { |
| open $input, '<', $texi or die "open $texi: $!"; |
| while (my $line = <$input>) { |
| if ($line =~ $nde) { |
| $node = &get_node($line); |
| } elsif ($line =~ $def) { |
| &process_annotation($line); |
| } elsif ($line =~ $list) { |
| &process_list($1); # @items occur in list or table context. |
| } elsif ($line =~ $stds) { |
| &record_error("Misplaced annotation", ["[$.] ".$line]); |
| } elsif ($line =~ $ign) { |
| while (<$input> !~ $eig) {} |
| } |
| } |
| close $input or die "close $texi: $!"; |
| } |
| |
| # Disabled until annotations are complete. |
| &print_errors() if %errors && 0; # Will exit(1). |
| |
| print("\@c DO NOT EDIT THIS FILE!\n". |
| "\@c This file is generated by $script from the Texinfo sources.\n". |
| "\@c The \@items are \@include'd from a \@table in header.texi.\n\n"); |
| |
| &print_entry($_) for sort keys %entries; |
| |
| # Processes an annotatable element, including any subsequent elements |
| # in an @*x chain, ensuring @standards are present, with valid syntax, |
| # either recording any errors detected or creating Summary entries. |
| # This function is the heart of the script. |
| # |
| # Prototypes and standards are gathered into separate lists and used |
| # to evaluate the completeness and correctness of annotations before |
| # generating the Summary entries. "Prototype" is used to refer to an |
| # element's entire definition while avoiding conflation with |
| # @def*-commands. "Element" is strictly used here to refer to the |
| # name extracted from the prototype, as used in @standardsx, for |
| # sorting the Summary. |
| sub process_annotation |
| { |
| my $line = shift; |
| my (@prototypes, @standards, $i, @tmp); |
| |
| # Gather prototypes and standards. |
| push @prototypes, $line; |
| while ($line = <$input>) { |
| last if $line !~ $ann; |
| push @prototypes, $line; |
| } |
| if ($line !~ $stds) { # The fundamental error. |
| return &record_error('Missing annotation', \@prototypes); |
| } |
| push @standards, $line; |
| push @standards, $line while ($line = <$input>) =~ $stds; |
| |
| # If next line is an @item, seek back to catch it on the next |
| # iteration. This avoids imposing a non-Texinfo syntax |
| # requirement of blank lines between consecutive annotated @items. |
| if ($line =~ $itm) { |
| seek $input, -length($line), 1 or die "seek: $!"; |
| } |
| |
| # Strict check for syntax errors. Other matches are loose, which |
| # aids error detection and reporting by ensuring things that look |
| # like standards aren't simply passed over, but caught here. |
| for ($i=0; $i<@standards; ++$i) { |
| my $standard = $standards[$i]; |
| if ($standard !~ $strict_std && $standard !~ $strict_stx) { |
| push @tmp, $standard; |
| } |
| } |
| return &record_error('Invalid syntax', \@tmp) if @tmp; |
| |
| # @standardsx should not be in non-@*x chains. |
| if (@prototypes == 1) { |
| for ($i=0; $i<@standards; ++$i) { |
| return &record_error('Misplaced @standardsx', \@prototypes) |
| if $standards[$i] =~ $stx; |
| } |
| } |
| # @standards may only occur once in @*x chains, at the beginning. |
| if (@prototypes > 1) { |
| for ($i=1; $i<@standards; ++$i) { |
| return &record_error('Misplaced @standards', \@prototypes) |
| if $standards[$i] =~ $std; |
| } |
| } |
| |
| # The @standards are aligned. |
| &add_entries(\@prototypes, \@standards); |
| } |
| |
| # Goes through the prototypes, cleaning them up and extracting the |
| # elements, pairing them with the appropriate annotations to create |
| # Summary entries. |
| sub add_entries |
| { |
| my ($prototypes, $standards) = @_; |
| my $isx = @{$prototypes} > 1 ? 1 : 0; |
| my $allx = $standards->[0] =~ $stx ? 1 : 0; |
| my ($defstd, $defhdr, %standardsx, $i, $j); |
| |
| # Grab the default annotation and index any @standardsx. Take |
| # care in case there is no default. |
| if ($isx) { |
| if (!$allx) { |
| ($defstd, $defhdr) |
| = $standards->[0] =~ /${std}([^,]+), (.*)\}$/; |
| } |
| for ($i = $allx ? 0 : 1; $i<@{$standards}; ++$i) { |
| my ($e, $s, $h) |
| = $standards->[$i] =~ /${stx}([^,]+), ([^,]+), (.*)\}$/; |
| push @{$standardsx{$e}{hs}}, [$h, $s]; |
| } |
| } |
| |
| for ($i=0; $i<@{$prototypes}; ++$i) { |
| my $e = &get_element($prototypes->[$i]); |
| my $p = &get_prototype($prototypes->[$i]); |
| my ($s, $h); |
| if ($isx && exists $standardsx{$e}) { |
| for ($j=0; $j<@{$standardsx{$e}{hs}}; ++$j) { |
| $h = $standardsx{$e}{hs}[$j]->[0]; |
| $s = $standardsx{$e}{hs}[$j]->[1]; |
| &record_entry($e, $p, $h, $s, $node); |
| ++$standardsx{$e}{seen}; |
| } |
| } elsif ($isx && $allx) { |
| &record_error('Missing annotation', [$prototypes->[$i]]); |
| } elsif ($isx) { |
| &record_entry($e, $p, $defhdr, $defstd, $node); |
| } else { |
| for ($j=0; $j<@{$standards}; ++$j) { |
| ($s, $h) = $standards->[$j] =~ /${std}([^,]+), ([^,\}]+)\}$/; |
| &record_entry($e, $p, $h, $s, $node); |
| } |
| } |
| } |
| |
| # Check if there were any unmatched @standardsx. |
| for my $e (keys %standardsx) { |
| if (!exists $standardsx{$e}{seen}) { |
| &record_error('Spurious @standardsx', [$e."\n"]) |
| } |
| } |
| } |
| |
| # Stores a Summary entry in %entries. May be called multiple times |
| # per element if multiple header and standard annotations exist. Also |
| # keys on prototypes, as some elements have multiple prototypes. See |
| # isnan in arith.texi for one example. |
| sub record_entry |
| { |
| my ($ele, $proto, $hdr, $std, $node) = @_; |
| push @{$entries{$ele}{$proto}}, [$hdr, $std, $node]; |
| } |
| |
| # Processes list or table contexts, with nesting. |
| sub process_list |
| { |
| my $type = shift; |
| my $in_vtbl = $type eq "vtable" ? 1 : 0; |
| |
| while (my $line = <$input>) { |
| if ($line =~ $itms) { |
| next if ! $in_vtbl; # Not an annotatable context. |
| &process_annotation($line); |
| } elsif ($line =~ $def) { |
| &process_annotation($line); |
| } elsif ($line =~ $stds) { |
| &record_error('Misplaced annotation', ["[$.] ".$line]); |
| } elsif ($line =~ $endl) { |
| return; # All done. |
| } elsif ($line =~ $list) { |
| &process_list($1); # Nested list. |
| } |
| } |
| } |
| |
| # Returns the current node from an @node line. Used for referencing |
| # from the Summary. |
| sub get_node |
| { |
| my $line = shift; |
| chomp $line; |
| $line =~ s/$nde//; |
| my ($n) = split ',', $line; |
| return $n |
| } |
| |
| # Returns the cleaned up prototype from @def|item* lines. |
| sub get_prototype |
| { |
| my $dfn = shift; |
| chomp $dfn; |
| $dfn =~ s/\s+/ /g; # Collapse whitespace. |
| $dfn =~ s/ \{([^\}]*)\} / $1 /g; # Remove grouping braces. |
| $dfn =~ s/^\@\S+ //; # Remove @-command. |
| $dfn =~ s/^Macro //i; # Scrape off cruft... |
| $dfn =~ s/^Data Type //i; |
| $dfn =~ s/^Variable //i; |
| $dfn =~ s/^Deprecated Function //i; |
| $dfn =~ s/^SVID Macro //i; |
| $dfn =~ s/^Obsolete function //i; |
| $dfn =~ s/^Constant //i; |
| $dfn =~ s/^Type //i; |
| $dfn =~ s/^Function //i; |
| $dfn =~ s/^\{(.*)\}$/$1/; # Debrace yourself. |
| $dfn =~ s/^\{([^\}]*)\} /$1 /; # These ones too. |
| return $dfn; |
| } |
| |
| # Returns an annotated element's name. |
| # |
| # Takes a line defining an annotatable element (e.g., @def|item*), |
| # splitting it on whitespace. The element is generally detected as |
| # the member immediately preceding the first parenthesized expression |
| # (e.g., a function), or the last token in the list. Some additional |
| # cleanup is applied to the element before returning it. |
| sub get_element |
| { |
| my $i = 0; |
| my @toks = split /\s+/, shift; |
| # tzname array uses '['; don't match function pointers. |
| ++$i while $toks[$i] && $toks[$i] !~ /^[\(\[](?!\*)/; |
| $toks[$i-1] =~ s/^\*//; # Strip pointer type syntax. |
| $toks[$i-1] =~ s/^\{?([^\}]+)\}?$/$1/; # Strip braces. |
| $toks[$i-1] =~ s/^\(\*([^\)]+)\)$/$1/; # Function pointers. |
| return $toks[$i-1]; |
| } |
| |
| # Records syntax errors detected in the manual related to @standards. |
| # The @def|item*s are grouped by file, then errors, to make it easier |
| # to track down exactly where and what the problems are. |
| sub record_error |
| { |
| my ($err, $list) = @_; |
| push @{$errors{$texi}{$err}}, $_ for (@{$list}); |
| return 0; |
| } |
| |
| # Reports all detected errors and exits with failure. Indentation is |
| # used for readability, and "ERROR" is used for visibility. |
| sub print_errors |
| { |
| for $texi (sort keys %errors) { |
| print STDERR "ERRORS in $texi:\n"; |
| for my $err (sort keys %{$errors{$texi}}) { |
| print STDERR " $err:\n"; |
| print STDERR " $_" for (@{$errors{$texi}{$err}}); |
| } |
| } |
| print(STDERR "\nFor a description of expected syntax, see ". |
| "\`$script --help'\n\n"); |
| exit 1; |
| } |
| |
| # Prints an entry in the Summary. |
| # |
| # All the blank lines in summary.texi may seem strange at first, but |
| # they have significant impact on how Texinfo renders the output. |
| # Essentially, each line is its own paragraph. There is a @comment |
| # with the element name, arguably unnecessary, but useful for seeing |
| # the sorting order and extracted element names, and maintains the |
| # format established by summary.awk. Each @item in the @table is the |
| # prototype, which may be anything from just a variable name to a |
| # function declaration. The body of each @item contains lines |
| # annotating the headers and standards each element is declared |
| # in/comes from, with a reference to the @node documenting the element |
| # wrt. each header and standard combination. |
| sub print_entry |
| { |
| my $element = shift; |
| for my $prototype (sort keys %{$entries{$element}}) { |
| print "\@comment $element\n\@item $prototype\n\n"; |
| for (@{$entries{$element}{$prototype}}) { |
| my ($header, $standard, $node) |
| = ($_->[0], $_->[1], $_->[2]); |
| if ($header =~ /^\(none\)$/i) { |
| $header = "\@emph{no header}"; |
| } elsif ($header =~ /\(optional\)$/) { |
| $header =~ s/^(\S+) \((.*)\)$/\@file{$1} \@emph{$2}/; |
| } elsif ($header ne '???') { |
| $header = "\@file{$header}"; |
| } |
| print "$header ($standard): \@ref{$node}.\n\n"; |
| } |
| } |
| } |
| |
| # Document the syntax of @standards. |
| sub help |
| { |
| print "$script "; |
| print <<'EOH'; |
| generates the Summary of Library Facilities (summary.texi) |
| from @standards and @standardsx macros in the Texinfo sources (see |
| macros.texi). While generating the Summary, it also checks that |
| @standards are used, correctly. |
| |
| In general, any @def*-command or @item in a @vtable is considered |
| annotatable. "Misplaced annotation" refers to @standards macros |
| detected outside an annotatable context. "Missing annotation" refers |
| to annotatable elements without @standards. @standards are expected |
| to immediately follow the elements being annotated. In @*x lists, |
| @standards sets the default annotation and may only occur as the first |
| annotation ("Misplaced @standards"). @standardsx may not be used |
| outside @*x lists ("Misplaced @standardsx"). "Spurious @standardsx" |
| refers to otherwise valid @standardsx macros that were not matched to |
| an element in an @*x list. "Invalid syntax" means just that. |
| |
| The syntax of @standards annotations is designed to accomodate |
| multiple header and standards annotations, as necessary. |
| |
| Examples: |
| |
| @deftp FOO |
| @standards{STD, HDR} |
| |
| @defvar BAR |
| @standards{STD, HDR1} |
| @standards{STD, HDR2} |
| |
| @deftypefun foo |
| @deftypefunx fool |
| @standards{STD, HDR} |
| |
| @item bar |
| @itemx baz |
| @standardsx{bar, STD1, HDR1} |
| @standardsx{baz, STD1, HDR1} |
| @standardsx{baz, STD2, HDR2} |
| |
| Note that @standardsx deviates from the usual Texinfo syntax in that |
| it is optional and may be used without @standards. |
| EOH |
| ; exit 0; |
| } |