B-Fathom

 view release on metacpan or  search on metacpan

Fathom.pm  view on Meta::CPAN


=head1 NAME

B::Fathom - a module to evaluate the readability of Perl code

=head1 SYNOPSIS

    perl -MO=Fathom <script>

or

    perl -MO=Fathom,-v <script>

where E<lt>scriptE<gt> is the name of the Perl program that you
want to evaluate.

C<-v> activates verbose mode, which currently reports which subs have been
skipped over because they seem to be imported.  One can also indicate C<-vN>,
where C<N> is some number greater than zero, to provide I<even more> verbose
diagnostics.  The specifics of these modes may change in future releases.  See
comments in the code for further information.

There is also an OO interface, which can be used as follows:

    my $fathom  = B::Fathom->new('-v');
    my $score   = $fathom->fathom(\&foo);

See METHODS below for a more complete explanation of the OO interface.

=head1 DESCRIPTION

C<B::Fathom> is a backend to the Perl compiler; it analyzes the syntax
of your Perl code, and estimates the readability of your program.

Currently, this module's idea of `readability' is based on methods
used for analyzing readability of English prose.  Further extensions
are intended.

=head1 METHODS

There is a simple object-oriented interface to B::Fathom.  It consists of two
methods:

=over 4

=item new(@args)

This method constructs a new compiler object.  The optional @args indicate
compiler options; see SYNOPSIS for a list.

=item fathom(@subrefs)

This method grades the subroutines referred to by @subrefs, and returns
their score as a string.

=back

=head1 CAVEATS

Because of the nature of the compiler, C<Fathom> has to do some
guessing about the syntax of your program.  See the comments in the
module for specifics.

C<Fathom> doesn't work very well on modules yet.

=head1 AUTHOR

Kurt Starsinic E<lt>F<kstar@cpan.org>E<gt>

=head1 COPYRIGHT

    Copyright (c) 1998, 1999, 2000 Kurt Starsinic.
    This module is free software; you may redistribute it
    and/or modify it under the same terms as Perl itself.

=cut


# TODO:
#   Incorporate Halstead's effort equation and McCabe's cyclomatic metric.
#   Process format statements, prototypes, and package statements.
#   Do a more accurate job when processing modules, rather than scripts.
#   Be smarter about parentheses.
#   Find a `cooler' way to dereference CV's than using symbolic refs.


my (%Taken, %Name, @Skip_sub, @Subs_queue);
my ($Tok, $Expr, $State, $Sub) = (0, 0, 0, 0);
my $Verbose = 0;
my (%Boring) = (
    pp_null         => 1,
    pp_enter        => 1,
    pp_pushmark     => 1,
    pp_unstack      => 1,
    pp_lineseq      => 1,
    pp_stub         => 1,
);


# The `compile' subroutine is the meat of any compiler backend; see
# the documentation for B.pm for details.
sub compile
{
    my (@args)  = @_;

    _parse_args(@args);

    return sub { print do_compile() }
}


# This subroutine is called by either the compiler backend mechanism
# (via -MO=Fathom) or via the OO interface (via fathom()).  If no
# parameters were passed in, this is a call from the compiler backend,
# and we fathom all of the code in main::.  If parameters _are_ passed
# in, then they're a list of references to subroutines to fathom.
sub do_compile
{
    my (@subrefs)   = @_;
    my $preamble    = "";

Fathom.pm  view on Meta::CPAN

    my $output  = "";

    # Normalize EMBED and non-EMBED ppaddr's:
    $ppaddr =~ s/^Perl_// or                                # Historic
    $ppaddr =~ s/^PL_ppaddr\[OP_(\w+)\]/'pp_' . lc $1/e;    # 5.6.0+

    if ($self->can('line')) {
       $output  = perline();
       $linenum = $self->line;
    }

    $output .= sprintf("%3d %-15s %s\n", $linenum, $ppaddr, ref($self))
        if $Verbose > 1;

    my ($TokOld, $ExprOld, $StateOld, $SubOld)  = ($Tok, $Expr, $State, $Sub);

    if      ($Boring{$ppaddr}) {
        # Do nothing; these OPs don't count
    } elsif ($ppaddr eq 'pp_nextstate' or $ppaddr eq 'pp_dbstate') {
        $Tok += 1;             $State += 1;
    } elsif ($ppaddr eq 'pp_leavesub') {    # sub name { <xxx> }
        $Tok += 4; $Expr += 1; $State += 1; $Sub += 1;
    } elsif ($ppaddr =~ /^pp_leave/) {
        # pp_leave* is already accounted for in its matching pp_enter*
    } elsif ($ppaddr eq 'pp_entertry') {    # eval { <xxx> }
        $Tok += 3; $Expr += 1;
    } elsif ($ppaddr eq 'pp_anoncode') {    # sub { <xxx> }
        $Tok += 3; $Expr += 1;
    } elsif ($ppaddr eq 'pp_scope') {       # do { <xxx> }
        $Tok += 3; $Expr += 1;
    } elsif ($ppaddr eq 'pp_entersub') {    # foo()
        $Tok += 3; $Expr += 1;
    } elsif ($self->isa('B::LOOP')) {       # for (<xxx>) { <yyy> }
        $Tok += 5; $Expr += 2;
    } elsif ($self->isa('B::LISTOP')) {     # OP(<xxx>)
        $Tok += 3; $Expr += 1;
    } elsif ($self->isa('B::BINOP')) {      # <xxx> OP <yyy>
        $Tok += 1; $Expr += 1;
    } elsif ($self->isa('B::LOGOP')) {      # <xxx> OP <yyy>
        $Tok += 1; $Expr += 1;
    } elsif ($self->isa('B::CONDOP')) {     # while (<xxx>) { <yyy> }
        $Tok += 5; $Expr += 2;
    } elsif ($self->isa('B::UNOP')) {       # OP <xxx>
        $Tok += 1; $Expr += 1;
    } else {                                # OP
        $Tok += 1;
    }

    if (defined $linenum) {
        $TokPerLine{$linenum}   += $Tok   - $TokOld;
        $ExprPerLine{$linenum}  += $Expr  - $ExprOld;
        $StatePerLine{$linenum} += $State - $StateOld;
        $SubPerLine{$linenum}   += $Sub   - $SubOld;
    }

    return $output;
}


# Keep track of the sub associated with each symbol.  If we find multiple
# symbol table entries pointing to one sub, then we'll guess (in
# do_compile()) that the sub is imported, and we'll ignore it.  Thanks
# to Mark-Jason Dominus for suggesting this strategy.
sub B::OBJECT::tally_symrefs
{
    my ($symbol)    = @_;
    my $name        = full_subname($symbol);

    # We're creating a `symbolic reference' in this block
    # (see perlref(1)), which is why we need `no strict':
    if ($name) {
        no strict;
        my $coderef = \&{"$name"};

        $Taken{$coderef}++;
        $Name{$coderef} = $name;
    }
}


# Create an array of OP's for introspection.  These are the `root' OP's
# of each sub that we're going to examine.
sub B::OBJECT::queue_subs
{
    my ($symbol)    = @_;
    my $name        = full_subname($symbol);

    # We're creating a `symbolic reference' in this block
    # (see perlref(1)), which is why we need `no strict':
    if ($name) {
        no strict;
        my $coderef = \&{"$name"};

        push @Subs_queue, $symbol->CV->ROOT unless $Taken{$coderef} > 1;
    }
}


# Given a symbol table entry $symbol, return the fully qualified subroutine
# name of the associated subroutine; if there is none, return undef.
sub full_subname
{
    my ($symbol)    = @_;

    # Build the full subname from the stashname and the symbolname:
    if ($symbol->CV->isa('B::CV')) {
        return $symbol->STASH->NAME . "::" . $symbol->NAME;
    } else {
        return undef;
    }
}


sub _parse_args
{
    my (@args)  = @_;

    foreach (@args) {
        if (/-v(.*)/) { $Verbose = length($1) ? $1 : 1 }
        else          { die "Unknown argument:  `$_'" }
    }



( run in 2.074 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )