| #! /usr/bin/perl -w | |
| # Extract all examples from the manual source. | |
| # This file is part of GNU Bison | |
| # Copyright (C) 1992, 2000-2001, 2005-2006, 2009-2015, 2018-2021 Free | |
| # Software Foundation, Inc. | |
| # | |
| # This program is free software: you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation, either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # This program 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 General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
| # Usage: extexi [OPTION...] input-file.texi ... -- [FILES to extract] | |
| # Look for @example environments preceded with lines such as: | |
| # | |
| # @comment file c/mfcalc/calc.y | |
| # or | |
| # @comment file c/mfcalc/calc.y: 3 | |
| # | |
| # and output their content in that file (c/mfcalc/calc.y). When | |
| # numbers are provided, use them to decide the output order (block 1 | |
| # is output before block 2, even if the latter appears before). The | |
| # same number may be used several time, in which case the order of | |
| # appearance is used. | |
| # | |
| # Use @ignore for code to extract that must not be part of the | |
| # documentation. For instance: | |
| # | |
| # @ignore | |
| # @comment file: c++/calc++/scanner.ll | |
| # @example | |
| # // Work around an incompatibility in Flex. | |
| # # undef yywrap | |
| # # define yywrap() 1 | |
| # @end example | |
| # @end ignore | |
| use strict; | |
| use File::Basename qw(dirname); | |
| use File::Path qw(make_path); | |
| # Whether we generate synclines. | |
| my $synclines = 0; | |
| # normalize($block) | |
| # ----------------- | |
| # Remove Texinfo mark up. | |
| sub normalize($) | |
| { | |
| local ($_) = @_; | |
| # If we just remove this lines, then the compiler's tracking of | |
| # #lines is broken. Leave lines that that accepted by all our tools | |
| # (including flex, hence the leading space), and that will be easy | |
| # to remove (see the Make examples-unline recipe). | |
| s{^\@(c |comment|dots|end (ignore|group)|ignore|group).*}{ /**/}mg; | |
| s/\@value\{VERSION\}/$ENV{VERSION}/g; | |
| s/^\@(error|result)\{\}//mg; | |
| s/\@([{}@])/$1/g; | |
| s/\@comment.*//; | |
| $_; | |
| } | |
| # Print messages only once. | |
| my %msg; | |
| sub message($) | |
| { | |
| my ($msg) = @_; | |
| if (! $msg{$msg}) | |
| { | |
| print STDERR "extexi: $msg\n"; | |
| $msg{$msg} = 1; | |
| } | |
| } | |
| # The list of files we should extract. | |
| my @file_wanted; | |
| # Whether we should extract that file, and then under which path. We | |
| # check if the prefix matches. So for instance if "foo/bar.y" is | |
| # wanted (i.e., in @FILE_WANTED), "file: bar.y" matches. | |
| sub file_wanted ($) | |
| { | |
| my ($f) = @_; | |
| for my $file (@file_wanted) | |
| { | |
| # No endswith in Perl 5... | |
| return $file if $f eq substr($file, -length($f)); | |
| } | |
| undef | |
| } | |
| # process ($in) | |
| # ------------- | |
| # Read input file $in, and generate the outputs. | |
| sub process ($) | |
| { | |
| my ($in) = @_; | |
| use IO::File; | |
| my $f = new IO::File($in) | |
| or die "$in: cannot open: $?"; | |
| # FILE-NAME => { BLOCK-NUM => CODE } | |
| my %file; | |
| # The latest "@comment file: FILE [BLOCK-NUM]" arguments. | |
| my $file; | |
| my $block; | |
| # The @example block currently read. | |
| my $input; | |
| local $_; | |
| while (<$f>) | |
| { | |
| if (/^\@comment file: ([^:\n]+)(?::\s*(\d+))?$/) | |
| { | |
| my $f = $1; | |
| $block = $2 || 1; | |
| if (file_wanted($f)) | |
| { | |
| $file = file_wanted($f); | |
| message(" GEN $file"); | |
| } | |
| else | |
| { | |
| message("SKIP $f"); | |
| } | |
| } | |
| elsif ($file && /^\@(small)?example$/ .. /^\@end (small)?example$/) | |
| { | |
| if (/^\@(small)?example$/) | |
| { | |
| # Bison supports synclines, but not Flex. | |
| $input .= sprintf ("#line %s \"$in\"\n", $. + 1) | |
| if $synclines && $file =~ /\.[chy]*$/; | |
| } | |
| elsif (/^\@end (small)?example$/) | |
| { | |
| die "no contents: $file" | |
| if $input eq ""; | |
| $file{$file}{$block} .= "\n" if defined $file{$file}{$block}; | |
| $file{$file}{$block} .= normalize($input); | |
| $file = $input = undef; | |
| ++$block; | |
| } | |
| else | |
| { | |
| $input .= $_; | |
| } | |
| } | |
| } | |
| # Output the files. | |
| for my $file (keys %file) | |
| { | |
| make_path (dirname ($file)); | |
| my $o = new IO::File(">$file") | |
| or die "$file: cannot create: $?"; | |
| print $o $file{$file}{$_} | |
| for sort keys %{$file{$file}}; | |
| } | |
| } | |
| my @input; | |
| my $seen_dash = 0; | |
| for my $arg (@ARGV) | |
| { | |
| if ($seen_dash) | |
| { | |
| push @file_wanted, $arg; | |
| } | |
| elsif ($arg eq '--') | |
| { | |
| $seen_dash = 1; | |
| } | |
| elsif ($arg eq '--synclines') | |
| { | |
| $synclines = 1; | |
| } | |
| else | |
| { | |
| push @input, $arg; | |
| } | |
| } | |
| process $_ | |
| foreach @input; | |
| ### Setup "GNU" style for perl-mode and cperl-mode. | |
| ## Local Variables: | |
| ## perl-indent-level: 2 | |
| ## perl-continued-statement-offset: 2 | |
| ## perl-continued-brace-offset: 0 | |
| ## perl-brace-offset: 0 | |
| ## perl-brace-imaginary-offset: 0 | |
| ## perl-label-offset: -2 | |
| ## cperl-indent-level: 2 | |
| ## cperl-brace-offset: 0 | |
| ## cperl-continued-brace-offset: 0 | |
| ## cperl-label-offset: -2 | |
| ## cperl-extra-newline-before-brace: t | |
| ## cperl-merge-trailing-else: nil | |
| ## cperl-continued-statement-offset: 2 | |
| ## End: |