1#!/usr/bin/env perl 2# SPDX-License-Identifier: GPL-2.0 3# Copyright (c) 2016 by Mauro Carvalho Chehab <mchehab@kernel.org>. 4 5use strict; 6use Text::Tabs; 7use Getopt::Long; 8use Pod::Usage; 9 10my $debug; 11my $help; 12my $man; 13 14GetOptions( 15 "debug" => \$debug, 16 'usage|?' => \$help, 17 'help' => \$man 18) or pod2usage(2); 19 20pod2usage(1) if $help; 21pod2usage(-exitstatus => 0, -verbose => 2) if $man; 22pod2usage(2) if (scalar @ARGV < 2 || scalar @ARGV > 3); 23 24my ($file_in, $file_out, $file_exceptions) = @ARGV; 25 26my $data; 27my %ioctls; 28my %defines; 29my %typedefs; 30my %enums; 31my %enum_symbols; 32my %structs; 33 34require Data::Dumper if ($debug); 35 36# 37# read the file and get identifiers 38# 39 40my $is_enum = 0; 41my $is_comment = 0; 42open IN, $file_in or die "Can't open $file_in"; 43while (<IN>) { 44 $data .= $_; 45 46 my $ln = $_; 47 if (!$is_comment) { 48 $ln =~ s,/\*.*(\*/),,g; 49 50 $is_comment = 1 if ($ln =~ s,/\*.*,,); 51 } else { 52 if ($ln =~ s,^(.*\*/),,) { 53 $is_comment = 0; 54 } else { 55 next; 56 } 57 } 58 59 if ($is_enum && $ln =~ m/^\s*([_\w][\w\d_]+)\s*[\,=]?/) { 60 my $s = $1; 61 my $n = $1; 62 $n =~ tr/A-Z/a-z/; 63 $n =~ tr/_/-/; 64 65 $enum_symbols{$s} = "\\ :ref:`$s <$n>`\\ "; 66 67 $is_enum = 0 if ($is_enum && m/\}/); 68 next; 69 } 70 $is_enum = 0 if ($is_enum && m/\}/); 71 72 if ($ln =~ m/^\s*#\s*define\s+([_\w][\w\d_]+)\s+_IO/) { 73 my $s = $1; 74 my $n = $1; 75 $n =~ tr/A-Z/a-z/; 76 77 $ioctls{$s} = "\\ :ref:`$s <$n>`\\ "; 78 next; 79 } 80 81 if ($ln =~ m/^\s*#\s*define\s+([_\w][\w\d_]+)\s+/) { 82 my $s = $1; 83 my $n = $1; 84 $n =~ tr/A-Z/a-z/; 85 $n =~ tr/_/-/; 86 87 $defines{$s} = "\\ :ref:`$s <$n>`\\ "; 88 next; 89 } 90 91 if ($ln =~ m/^\s*typedef\s+([_\w][\w\d_]+)\s+(.*)\s+([_\w][\w\d_]+);/) { 92 my $s = $2; 93 my $n = $3; 94 95 $typedefs{$n} = "\\ :c:type:`$n <$s>`\\ "; 96 next; 97 } 98 if ($ln =~ m/^\s*enum\s+([_\w][\w\d_]+)\s+\{/ 99 || $ln =~ m/^\s*enum\s+([_\w][\w\d_]+)$/ 100 || $ln =~ m/^\s*typedef\s*enum\s+([_\w][\w\d_]+)\s+\{/ 101 || $ln =~ m/^\s*typedef\s*enum\s+([_\w][\w\d_]+)$/) { 102 my $s = $1; 103 104 $enums{$s} = "enum :c:type:`$s`\\ "; 105 106 $is_enum = $1; 107 next; 108 } 109 if ($ln =~ m/^\s*struct\s+([_\w][\w\d_]+)\s+\{/ 110 || $ln =~ m/^\s*struct\s+([[_\w][\w\d_]+)$/ 111 || $ln =~ m/^\s*typedef\s*struct\s+([_\w][\w\d_]+)\s+\{/ 112 || $ln =~ m/^\s*typedef\s*struct\s+([[_\w][\w\d_]+)$/ 113 ) { 114 my $s = $1; 115 116 $structs{$s} = "struct $s\\ "; 117 next; 118 } 119} 120close IN; 121 122# 123# Handle multi-line typedefs 124# 125 126my @matches = ($data =~ m/typedef\s+struct\s+\S+?\s*\{[^\}]+\}\s*(\S+)\s*\;/g, 127 $data =~ m/typedef\s+enum\s+\S+?\s*\{[^\}]+\}\s*(\S+)\s*\;/g,); 128foreach my $m (@matches) { 129 my $s = $m; 130 131 $typedefs{$s} = "\\ :c:type:`$s`\\ "; 132 next; 133} 134 135# 136# Handle exceptions, if any 137# 138 139my %def_reftype = ( 140 "ioctl" => ":ref", 141 "define" => ":ref", 142 "symbol" => ":ref", 143 "typedef" => ":c:type", 144 "enum" => ":c:type", 145 "struct" => ":c:type", 146); 147 148if ($file_exceptions) { 149 open IN, $file_exceptions or die "Can't read $file_exceptions"; 150 while (<IN>) { 151 next if (m/^\s*$/ || m/^\s*#/); 152 153 # Parsers to ignore a symbol 154 155 if (m/^ignore\s+ioctl\s+(\S+)/) { 156 delete $ioctls{$1} if (exists($ioctls{$1})); 157 next; 158 } 159 if (m/^ignore\s+define\s+(\S+)/) { 160 delete $defines{$1} if (exists($defines{$1})); 161 next; 162 } 163 if (m/^ignore\s+typedef\s+(\S+)/) { 164 delete $typedefs{$1} if (exists($typedefs{$1})); 165 next; 166 } 167 if (m/^ignore\s+enum\s+(\S+)/) { 168 delete $enums{$1} if (exists($enums{$1})); 169 next; 170 } 171 if (m/^ignore\s+struct\s+(\S+)/) { 172 delete $structs{$1} if (exists($structs{$1})); 173 next; 174 } 175 if (m/^ignore\s+symbol\s+(\S+)/) { 176 delete $enum_symbols{$1} if (exists($enum_symbols{$1})); 177 next; 178 } 179 180 # Parsers to replace a symbol 181 my ($type, $old, $new, $reftype); 182 183 if (m/^replace\s+(\S+)\s+(\S+)\s+(\S+)/) { 184 $type = $1; 185 $old = $2; 186 $new = $3; 187 } else { 188 die "Can't parse $file_exceptions: $_"; 189 } 190 191 if ($new =~ m/^\:c\:(data|func|macro|type)\:\`(.+)\`/) { 192 $reftype = ":c:$1"; 193 $new = $2; 194 } elsif ($new =~ m/\:ref\:\`(.+)\`/) { 195 $reftype = ":ref"; 196 $new = $1; 197 } else { 198 $reftype = $def_reftype{$type}; 199 } 200 $new = "$reftype:`$old <$new>`"; 201 202 if ($type eq "ioctl") { 203 $ioctls{$old} = $new if (exists($ioctls{$old})); 204 next; 205 } 206 if ($type eq "define") { 207 $defines{$old} = $new if (exists($defines{$old})); 208 next; 209 } 210 if ($type eq "symbol") { 211 $enum_symbols{$old} = $new if (exists($enum_symbols{$old})); 212 next; 213 } 214 if ($type eq "typedef") { 215 $typedefs{$old} = $new if (exists($typedefs{$old})); 216 next; 217 } 218 if ($type eq "enum") { 219 $enums{$old} = $new if (exists($enums{$old})); 220 next; 221 } 222 if ($type eq "struct") { 223 $structs{$old} = $new if (exists($structs{$old})); 224 next; 225 } 226 227 die "Can't parse $file_exceptions: $_"; 228 } 229} 230 231if ($debug) { 232 print Data::Dumper->Dump([\%ioctls], [qw(*ioctls)]) if (%ioctls); 233 print Data::Dumper->Dump([\%typedefs], [qw(*typedefs)]) if (%typedefs); 234 print Data::Dumper->Dump([\%enums], [qw(*enums)]) if (%enums); 235 print Data::Dumper->Dump([\%structs], [qw(*structs)]) if (%structs); 236 print Data::Dumper->Dump([\%defines], [qw(*defines)]) if (%defines); 237 print Data::Dumper->Dump([\%enum_symbols], [qw(*enum_symbols)]) if (%enum_symbols); 238} 239 240# 241# Align block 242# 243$data = expand($data); 244$data = " " . $data; 245$data =~ s/\n/\n /g; 246$data =~ s/\n\s+$/\n/g; 247$data =~ s/\n\s+\n/\n\n/g; 248 249# 250# Add escape codes for special characters 251# 252$data =~ s,([\_\`\*\<\>\&\\\\:\/\|\%\$\#\{\}\~\^]),\\$1,g; 253 254$data =~ s,DEPRECATED,**DEPRECATED**,g; 255 256# 257# Add references 258# 259 260my $start_delim = "[ \n\t\(\=\*\@]"; 261my $end_delim = "(\\s|,|\\\\=|\\\\:|\\;|\\\)|\\}|\\{)"; 262 263foreach my $r (keys %ioctls) { 264 my $s = $ioctls{$r}; 265 266 $r =~ s,([\_\`\*\<\>\&\\\\:\/]),\\\\$1,g; 267 268 print "$r -> $s\n" if ($debug); 269 270 $data =~ s/($start_delim)($r)$end_delim/$1$s$3/g; 271} 272 273foreach my $r (keys %defines) { 274 my $s = $defines{$r}; 275 276 $r =~ s,([\_\`\*\<\>\&\\\\:\/]),\\\\$1,g; 277 278 print "$r -> $s\n" if ($debug); 279 280 $data =~ s/($start_delim)($r)$end_delim/$1$s$3/g; 281} 282 283foreach my $r (keys %enum_symbols) { 284 my $s = $enum_symbols{$r}; 285 286 $r =~ s,([\_\`\*\<\>\&\\\\:\/]),\\\\$1,g; 287 288 print "$r -> $s\n" if ($debug); 289 290 $data =~ s/($start_delim)($r)$end_delim/$1$s$3/g; 291} 292 293foreach my $r (keys %enums) { 294 my $s = $enums{$r}; 295 296 $r =~ s,([\_\`\*\<\>\&\\\\:\/]),\\\\$1,g; 297 298 print "$r -> $s\n" if ($debug); 299 300 $data =~ s/enum\s+($r)$end_delim/$s$2/g; 301} 302 303foreach my $r (keys %structs) { 304 my $s = $structs{$r}; 305 306 $r =~ s,([\_\`\*\<\>\&\\\\:\/]),\\\\$1,g; 307 308 print "$r -> $s\n" if ($debug); 309 310 $data =~ s/struct\s+($r)$end_delim/$s$2/g; 311} 312 313foreach my $r (keys %typedefs) { 314 my $s = $typedefs{$r}; 315 316 $r =~ s,([\_\`\*\<\>\&\\\\:\/]),\\\\$1,g; 317 318 print "$r -> $s\n" if ($debug); 319 $data =~ s/($start_delim)($r)$end_delim/$1$s$3/g; 320} 321 322$data =~ s/\\ ([\n\s])/\1/g; 323 324# 325# Generate output file 326# 327 328my $title = $file_in; 329$title =~ s,.*/,,; 330 331open OUT, "> $file_out" or die "Can't open $file_out"; 332print OUT ".. -*- coding: utf-8; mode: rst -*-\n\n"; 333print OUT "$title\n"; 334print OUT "=" x length($title); 335print OUT "\n\n.. parsed-literal::\n\n"; 336print OUT $data; 337close OUT; 338 339__END__ 340 341=head1 NAME 342 343parse_headers.pl - parse a C file, in order to identify functions, structs, 344enums and defines and create cross-references to a Sphinx book. 345 346=head1 SYNOPSIS 347 348B<parse_headers.pl> [<options>] <C_FILE> <OUT_FILE> [<EXCEPTIONS_FILE>] 349 350Where <options> can be: --debug, --help or --usage. 351 352=head1 OPTIONS 353 354=over 8 355 356=item B<--debug> 357 358Put the script in verbose mode, useful for debugging. 359 360=item B<--usage> 361 362Prints a brief help message and exits. 363 364=item B<--help> 365 366Prints a more detailed help message and exits. 367 368=back 369 370=head1 DESCRIPTION 371 372Convert a C header or source file (C_FILE), into a ReStructured Text 373included via ..parsed-literal block with cross-references for the 374documentation files that describe the API. It accepts an optional 375EXCEPTIONS_FILE with describes what elements will be either ignored or 376be pointed to a non-default reference. 377 378The output is written at the (OUT_FILE). 379 380It is capable of identifying defines, functions, structs, typedefs, 381enums and enum symbols and create cross-references for all of them. 382It is also capable of distinguish #define used for specifying a Linux 383ioctl. 384 385The EXCEPTIONS_FILE contain two rules to allow ignoring a symbol or 386to replace the default references by a custom one. 387 388Please read Documentation/doc-guide/parse-headers.rst at the Kernel's 389tree for more details. 390 391=head1 BUGS 392 393Report bugs to Mauro Carvalho Chehab <mchehab@kernel.org> 394 395=head1 COPYRIGHT 396 397Copyright (c) 2016 by Mauro Carvalho Chehab <mchehab@kernel.org>. 398 399License GPLv2: GNU GPL version 2 <https://gnu.org/licenses/gpl.html>. 400 401This is free software: you are free to change and redistribute it. 402There is NO WARRANTY, to the extent permitted by law. 403 404=cut 405