| File: | lib/Parrot/Headerizer/Functions.pm |
| Coverage: | 98.9% |
| line | stmt | bran | cond | sub | code |
|---|---|---|---|---|---|
| 1 | # Copyright (C) 2004-2010, Parrot Foundation. | ||||
| 2 | # $Id$ | ||||
| 3 | |||||
| 4 | package Parrot::Headerizer::Functions; | ||||
| 5 | 2 2 2 | use strict; | |||
| 6 | 2 2 2 | use warnings; | |||
| 7 | 2 2 2 | use base qw( Exporter ); | |||
| 8 | 2 2 2 | use Data::Dumper;$Data::Dumper::Indent=1; | |||
| 9 | our @EXPORT_OK = qw( | ||||
| 10 | process_argv | ||||
| 11 | read_file | ||||
| 12 | write_file | ||||
| 13 | qualify_sourcefile | ||||
| 14 | replace_pod_item | ||||
| 15 | no_both_PARROT_EXPORT_and_PARROT_INLINE | ||||
| 16 | validate_prototype_args | ||||
| 17 | no_both_static_and_PARROT_EXPORT | ||||
| 18 | handle_split_declaration | ||||
| 19 | asserts_from_args | ||||
| 20 | shim_test | ||||
| 21 | handle_modified_args | ||||
| 22 | add_asserts_to_declarations | ||||
| 23 | add_newline_if_multiline | ||||
| 24 | func_modifies | ||||
| 25 | add_headerizer_markers | ||||
| 26 | ); | ||||
| 27 | |||||
| 28 - 71 | =head1 NAME
Parrot::Headerizer::Functions - Functions used in headerizer programs
=head1 SYNOPSIS
use Parrot::Headerizer::Functions qw(
print_headerizer_warnings
read_file
write_file
qualify_sourcefile
asserts_from_args
);
=head1 DESCRIPTION
This package holds (non-object-oriented) functions used in
F<tools/dev/headerizer.pl>.
=head1 SUBROUTINES
=head2 C<process_argv>
=over 4
=item * Purpose
Validate list of object files provided as arguments.
=item * Arguments
@ofiles = process_argv(@ARGV);
List of files specified on the command-line.
=item * Return Value
Validated list of object files.
=item * Comment
=back
=cut | ||||
| 72 | |||||
| 73 | sub process_argv { | ||||
| 74 | 2 | my @argv = @_; | |||
| 75 | 2 | die 'No files specified.' unless @argv; | |||
| 76 | 1 | my %ofiles; | |||
| 77 | 1 1 | ++$ofiles{$_} for @argv; | |||
| 78 | 1 | my @ofiles = sort keys %ofiles; | |||
| 79 | 1 | for (@ofiles) { | |||
| 80 | 3 | print "$_ is specified more than once.\n" if $ofiles{$_} > 1; | |||
| 81 | } | ||||
| 82 | 1 | return @ofiles; | |||
| 83 | } | ||||
| 84 | |||||
| 85 | |||||
| 86 - 109 | =head2 C<read_file()> =over 4 =item * Purpose Read a file into a string. =item * Arguments String holding name of file to be read. =item * Return Value String holding the file's content. =item * Comment We can't alias this to C<Parrot::BuildUtil::slurp_file()> because that function changes DOS line endings to Unix, which we don't necessarily want here. =back =cut | ||||
| 110 | |||||
| 111 | |||||
| 112 | sub read_file { | ||||
| 113 | 19 | my $filename = shift; | |||
| 114 | |||||
| 115 | 19 | open my $fh, '<', $filename or die "couldn't read '$filename': $!"; | |||
| 116 | 18 18 18 | my $text = do { local $/ = undef; <$fh> }; | |||
| 117 | 18 | close $fh; | |||
| 118 | |||||
| 119 | 18 | return $text; | |||
| 120 | } | ||||
| 121 | |||||
| 122 - 141 | =head2 C<write_file()> =over 4 =item * Purpose Write a file. =item * Arguments List of two scalars: string holding name of file to be written; text to be written to the file. =item * Return Value Implicitly returns true upon success. =back =cut | ||||
| 142 | |||||
| 143 | sub write_file { | ||||
| 144 | 4 | my $filename = shift; | |||
| 145 | 4 | my $text = shift; | |||
| 146 | |||||
| 147 | 4 | open my $fh, '>', $filename or die "couldn't write '$filename': $!"; | |||
| 148 | 4 4 | print {$fh} $text; | |||
| 149 | 4 | close $fh; | |||
| 150 | } | ||||
| 151 | |||||
| 152 - 204 | =head2 C<qualify_sourcefile()>
=over 4
=item * Purpose
Given the name of a C object file, derive the name of its C<.c> or C<.pmc>
source code file, verify that file's existence, read in its source code, and
verify the existence of the corresponding C<.h> file.
=item * Arguments
my ($sourcefile, $source_code, $hfile) =
qualify_sourcefile( {
ofile => $ofile,
PConfig => \%PConfig,
is_yacc => $is_yacc,
} );
Reference to hash with 3 key-value pairs:
=over 4
=item * C<ofile>
String holding name of C or yacc object file.
=item * C<PConfig>
Reference to Parrot configuration hash.
=item * C<is_yacc>
Boolean reporting whether the source code file is a yacc file or not.
=back
=item * Return Value
List of 3 scalars: String holding source code file, string holding the ssource
code, string holding header file (or C<none> if no header file is found).
=item * Comment
The subroutine will die if the value provided for C<ofile> does not have a
corresponding C<.c> file or if it is a yacc file. The subroutine will also
die if it cannot locate an C<HEADERIZER HFILE> directive in the source code
file. The subroutine will also die if any header file referenced from the
source code cannot be located.
=back
=cut | ||||
| 205 | |||||
| 206 | sub qualify_sourcefile { | ||||
| 207 | 14 | my $args = shift; | |||
| 208 | 14 | my $cfile = $args->{ofile}; | |||
| 209 | 14 | $cfile =~ s/\Q$args->{PConfig}->{o}\E$/.c/ or $args->{is_yacc} | |||
| 210 | or die "$cfile doesn't look like an object file"; | ||||
| 211 | |||||
| 212 | 13 | my $pmcfile = $args->{ofile}; | |||
| 213 | 13 | $pmcfile =~ s/\Q$args->{PConfig}->{o}\E$/.pmc/; | |||
| 214 | |||||
| 215 | 13 | my $from_pmc = -f $pmcfile && !$args->{is_yacc}; | |||
| 216 | |||||
| 217 | 13 | my $sourcefile = $from_pmc ? $pmcfile : $cfile; | |||
| 218 | |||||
| 219 | 13 | my $source_code = read_file( $sourcefile ); | |||
| 220 | 13 | die qq{can't find HEADERIZER HFILE directive in "$sourcefile"} | |||
| 221 | unless $source_code =~ | ||||
| 222 | m{ /\* \s+ HEADERIZER\ HFILE: \s+ ([^*]+?) \s+ \*/ }sx; | ||||
| 223 | |||||
| 224 | 12 | my $hfile = $1; | |||
| 225 | 12 | if ( ( $hfile ne 'none' ) && ( not -f $hfile ) ) { | |||
| 226 | 1 | die qq{"$hfile" not found (referenced from "$sourcefile")}; | |||
| 227 | } | ||||
| 228 | |||||
| 229 | 11 | return ($sourcefile, $source_code, $hfile); | |||
| 230 | } | ||||
| 231 | |||||
| 232 - 241 | =pod
$text = replace_pod_item( {
text => $text,
name => $name,
heading => $heading,
cfile_name => $cfile_name,
} );
=cut | ||||
| 242 | |||||
| 243 | sub replace_pod_item { | ||||
| 244 | 34 | my $args = shift; | |||
| 245 | $args->{text} =~ s/=item C<[^>]*\b$args->{name}\b[^>]*>\n+/$args->{heading}\n\n/sm | ||||
| 246 | 34 | or do { | |||
| 247 | 2 | warn "$args->{cfile_name}: $args->{name} has no POD\n" | |||
| 248 | # lexer funcs don't have to have POD | ||||
| 249 | unless $args->{name} =~ /^yy/; | ||||
| 250 | }; | ||||
| 251 | 34 | return $args->{text}; | |||
| 252 | } | ||||
| 253 | |||||
| 254 - 263 | =pod
no_both_PARROT_EXPORT_and_PARROT_INLINE( {
file => $file,
name => $name,
parrot_inline => $parrot_inline,
parrot_api => $parrot_api,
} );
=cut | ||||
| 264 | |||||
| 265 | sub no_both_PARROT_EXPORT_and_PARROT_INLINE { | ||||
| 266 | 71 | my $args = shift; | |||
| 267 | 71 | my $death = | |||
| 268 | "$args->{file} $args->{name}: Can't have both PARROT_EXPORT and PARROT_INLINE"; | ||||
| 269 | 71 | die $death if $args->{parrot_inline} && $args->{parrot_api}; | |||
| 270 | 70 | return 1; | |||
| 271 | } | ||||
| 272 | |||||
| 273 - 277 | =pod
@args = validate_prototype_args( $args, $proto );
=cut | ||||
| 278 | |||||
| 279 | sub validate_prototype_args { | ||||
| 280 | 71 | my ($args, $proto) = @_; | |||
| 281 | 71 | my @args = split( /\s*,\s*/, $args ); | |||
| 282 | 71 | for (@args) { | |||
| 283 | 171 | /\S+\s+\S+/ | |||
| 284 | || ( $_ eq '...' ) | ||||
| 285 | || ( $_ eq 'void' ) | ||||
| 286 | || ( $_ =~ /(PARROT|NULLOK|SHIM)_INTERP/ ) | ||||
| 287 | or die "Bad args in $proto"; | ||||
| 288 | } | ||||
| 289 | 70 | return @args; | |||
| 290 | } | ||||
| 291 | |||||
| 292 - 301 | =pod
($return_type, $is_static) = no_both_static_and_PARROT_EXPORT( {
file => $file,
name => $name,
return_type => $return_type,
parrot_api => $parrot_api,
} );
=cut | ||||
| 302 | |||||
| 303 | sub no_both_static_and_PARROT_EXPORT { | ||||
| 304 | 72 | my $args = shift; | |||
| 305 | 72 | my $is_static = 0; | |||
| 306 | 72 | $is_static = $2 if $args->{return_type} =~ s/^((static)\s+)?//i; | |||
| 307 | 72 | my $death = "$args->{file} $args->{name}: Impossible to have both static and PARROT_EXPORT"; | |||
| 308 | 72 | die $death if $args->{parrot_api} && $is_static; | |||
| 309 | 71 | return ($args->{return_type}, $is_static); | |||
| 310 | } | ||||
| 311 | |||||
| 312 - 319 | =pod
my $split_decl = handle_split_declaration(
$function_decl,
$line_len,
);
=cut | ||||
| 320 | |||||
| 321 | sub handle_split_declaration { | ||||
| 322 | 16 | my ($function_decl, $line_len) = @_; | |||
| 323 | 16 | my @doc_chunks = split /\s+/, $function_decl; | |||
| 324 | 16 | my $split_decl = ''; | |||
| 325 | 16 | my @line; | |||
| 326 | 16 | while (@doc_chunks) { | |||
| 327 | 134 | my $chunk = shift @doc_chunks; | |||
| 328 | 134 | if (length(join(' ', @line, $chunk)) <= $line_len) { | |||
| 329 | 119 | push @line, $chunk; | |||
| 330 | } | ||||
| 331 | else { | ||||
| 332 | 15 | $split_decl .= join(' ', @line) . "\n"; | |||
| 333 | 15 | @line=($chunk); | |||
| 334 | } | ||||
| 335 | } | ||||
| 336 | 16 | $split_decl .= join(' ', @line) . "\n"; | |||
| 337 | 16 | $split_decl =~ s/\n$//; | |||
| 338 | |||||
| 339 | 16 | return $split_decl; | |||
| 340 | } | ||||
| 341 | |||||
| 342 | sub asserts_from_args { | ||||
| 343 | 14 | my @args = @_; | |||
| 344 | 14 | my @asserts; | |||
| 345 | |||||
| 346 | 14 | for my $arg (@args) { | |||
| 347 | 38 | if ( $arg =~ m{(ARGIN|ARGOUT|ARGMOD|ARGFREE_NOTNULL|NOTNULL)\((.+)\)} ) { | |||
| 348 | 21 | my $var = $2; | |||
| 349 | 21 | if($var =~ /\(*\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)\s*\(/) { | |||
| 350 | # argument is a function pointer | ||||
| 351 | # Is this branch ever reached? | ||||
| 352 | 1 | $var = $1; | |||
| 353 | } | ||||
| 354 | else { | ||||
| 355 | # try to isolate the variable's name; | ||||
| 356 | # strip off everything before the final space or asterisk. | ||||
| 357 | 20 | $var =~ s{.+[* ]([^* ]+)$}{$1}; | |||
| 358 | # strip off a trailing "[]", if any. | ||||
| 359 | 20 | $var =~ s{\[\]$}{}; | |||
| 360 | } | ||||
| 361 | 21 | push( @asserts, "PARROT_ASSERT_ARG($var)" ); | |||
| 362 | } | ||||
| 363 | 38 | if( $arg eq 'PARROT_INTERP' ) { | |||
| 364 | 5 | push( @asserts, "PARROT_ASSERT_ARG(interp)" ); | |||
| 365 | } | ||||
| 366 | } | ||||
| 367 | |||||
| 368 | 14 | return (@asserts); | |||
| 369 | } | ||||
| 370 | |||||
| 371 - 375 | =pod
my @modified_args = shim_test($func, \@args);
=cut | ||||
| 376 | |||||
| 377 | sub shim_test { | ||||
| 378 | 14 | my ($func, $argsref) = @_; | |||
| 379 | 14 14 | my @args = @{$argsref}; | |||
| 380 | 14 | for my $arg (@args) { | |||
| 381 | 31 | if ( $arg =~ m{SHIM\((.+)\)} ) { | |||
| 382 | 3 | $arg = $1; | |||
| 383 | 3 | if ( $func->{is_static} || ( $arg =~ /\*/ ) ) { | |||
| 384 | 2 | $arg = "SHIM($arg)"; | |||
| 385 | } | ||||
| 386 | else { | ||||
| 387 | 1 | $arg = "NULLOK($arg)"; | |||
| 388 | } | ||||
| 389 | } | ||||
| 390 | } | ||||
| 391 | 14 | return @args; | |||
| 392 | } | ||||
| 393 | |||||
| 394 | sub handle_modified_args { | ||||
| 395 | 14 | my ($decl, $modified_args_ref) = @_; | |||
| 396 | 14 14 | my @modified_args = @{ $modified_args_ref }; | |||
| 397 | 14 | my $multiline = 0; | |||
| 398 | 14 | my $argline = join( ", ", @modified_args ); | |||
| 399 | 14 | if ( length( $decl . $argline ) <= 75 ) { | |||
| 400 | 6 | $decl = "$decl$argline)"; | |||
| 401 | } | ||||
| 402 | else { | ||||
| 403 | 8 | if ( $modified_args[0] =~ /^(?:(?:SHIM|PARROT)_INTERP|Interp)\b/ ) { | |||
| 404 | 7 | $decl .= ( shift @modified_args ); | |||
| 405 | 7 | $decl .= "," if @modified_args; | |||
| 406 | } | ||||
| 407 | 8 15 | $argline = join( ",", map { "\n\t$_" } @modified_args ); | |||
| 408 | 8 | $decl = "$decl$argline)"; | |||
| 409 | 8 | $multiline = 1; | |||
| 410 | } | ||||
| 411 | 14 | return ($decl, $multiline); | |||
| 412 | } | ||||
| 413 | |||||
| 414 | # $decl .= $multiline ? ";\n" : ";"; | ||||
| 415 | sub add_newline_if_multiline { | ||||
| 416 | 12 | my ($decl, $multiline) = @_; | |||
| 417 | 12 | $decl .= $multiline ? ";\n" : ";"; | |||
| 418 | 12 | return $decl; | |||
| 419 | } | ||||
| 420 | |||||
| 421 | sub add_asserts_to_declarations { | ||||
| 422 | 4 | my ($funcs_ref, $decls_ref) = @_; | |||
| 423 | 4 4 | foreach my $func (@{ $funcs_ref }) { | |||
| 424 | 11 | my $assert = "#define ASSERT_ARGS_" . $func->{name}; | |||
| 425 | 11 | if(length($func->{name}) > 29) { | |||
| 426 | 1 | $assert .= " \\\n "; | |||
| 427 | } | ||||
| 428 | 11 | $assert .= " __attribute__unused__ int _ASSERT_ARGS_CHECK = ("; | |||
| 429 | |||||
| 430 | 11 11 | my @asserts = asserts_from_args( @{ $func->{args} } ); | |||
| 431 | 11 | if(@asserts) { | |||
| 432 | 9 | $assert .= "\\\n "; | |||
| 433 | 9 | $assert .= join(" \\\n , ", @asserts); | |||
| 434 | } | ||||
| 435 | else { | ||||
| 436 | 2 | $assert .= "0"; | |||
| 437 | } | ||||
| 438 | 11 | $assert .= ")"; | |||
| 439 | 11 11 | push(@{ $decls_ref }, $assert); | |||
| 440 | } | ||||
| 441 | 4 4 | return @{ $decls_ref }; | |||
| 442 | } | ||||
| 443 | |||||
| 444 - 448 | =pod @mods = func_modifies($arg, \@mods); =cut | ||||
| 449 | |||||
| 450 | sub func_modifies { | ||||
| 451 | 34 | my ($arg, $modsref) = @_; | |||
| 452 | 34 34 | my @mods = @{$modsref}; | |||
| 453 | 34 | if ( $arg =~ m{ARG(?:MOD|OUT)(?:_NULLOK)?\((.+?)\)} ) { | |||
| 454 | 10 | my $modified = $1; | |||
| 455 | 10 | if ( $modified =~ s/.*\*/*/ ) { | |||
| 456 | # We're OK | ||||
| 457 | } | ||||
| 458 | else { | ||||
| 459 | 2 | $modified =~ s/.* (\w+)$/$1/ or die qq{Unable to figure out the modified parm out of "$modified"}; | |||
| 460 | } | ||||
| 461 | 9 | push( @mods, "FUNC_MODIFIES($modified)" ); | |||
| 462 | } | ||||
| 463 | 33 | return @mods; | |||
| 464 | } | ||||
| 465 - 474 | =pod
return add_headerizer_markers( {
function_decls => \@function_decls,
sourcefile => $sourcefile,
hfile => $hfile,
code => $source_code,
} );
=cut | ||||
| 475 | |||||
| 476 | sub add_headerizer_markers { | ||||
| 477 | 3 | my $args = shift; | |||
| 478 | |||||
| 479 | 3 3 | my $function_decls = join( "\n" => @{ $args->{function_decls} }); | |||
| 480 | 3 | my $STARTMARKER = qr{/\* HEADERIZER BEGIN: $args->{sourcefile} \*/\n}; | |||
| 481 | 3 | my $ENDMARKER = qr{/\* HEADERIZER END: $args->{sourcefile} \*/\n?}; | |||
| 482 | 3 | my $DO_NOT_TOUCH = q{/* Don't modify between HEADERIZER BEGIN / HEADERIZER END. Your changes will be lost. */}; | |||
| 483 | |||||
| 484 | 3 | $args->{code} =~ | |||
| 485 | s{($STARTMARKER)(?:.*?)($ENDMARKER)} | ||||
| 486 | {$1$DO_NOT_TOUCH\n\n$function_decls\n$DO_NOT_TOUCH\n$2}s | ||||
| 487 | or die "Need begin/end HEADERIZER markers for $args->{sourcefile} in $args->{hfile}\n"; | ||||
| 488 | |||||
| 489 | 3 | return $args->{code}; | |||
| 490 | } | ||||
| 491 | |||||
| 492 | 1; | ||||
| 493 | |||||
| 494 | # Local Variables: | ||||
| 495 | # mode: cperl | ||||
| 496 | # cperl-indent-level: 4 | ||||
| 497 | # fill-column: 100 | ||||
| 498 | # End: | ||||
| 499 | # vim: expandtab shiftwidth=4: | ||||