00001
00002 # =======================================================================
00003 # Doxygen Pre-Processor for Perl
00004 # Copyright (C) 2002 Bart Schuller
00005 # Copyright (C) 2006 Phinex Informatik AG
00006 # All Rights Reserved
00007 #
00008 # Doxygen Filter is free software; you can redistribute it and/or modify
00009 # it under the same terms as Perl itself.
00010 #
00011 # Larry Wall's 'Artistic License' for perl can be found in
00012 # http://www.perl.com/pub/a/language/misc/Artistic.html
00013 #
00014 # =======================================================================
00015 #
00016 # Author: Aeby Thomas, Phinex Informatik AG,
00017 # Based on DoxygenFilter from Bart Schuller
00018 # E-Mail: tom.aeby@phinex.ch
00019 #
00020 # Phinex Informatik AG
00021 # Thomas Aeby
00022 # Kirchweg 52
00023 # 1735 Giffers
00024 #
00025 # =======================================================================
00026 #
00027 # @(#) $Id: PerlFilter.pm,v 1.6 2009/01/08 09:32:48 aeby Exp $
00028 #
00029 # Revision History:
00030 #
00031 # $Log: PerlFilter.pm,v $
00032 # Revision 1.6 2009/01/08 09:32:48 aeby
00033 # added support for @var command (suggested by Mike Richardson)
00034 #
00035 # Revision 1.5 2006/02/15 15:59:29 aeby
00036 # filter(): "@method" handling: drop auto-recognized $self argument at
00037 # first position
00038 #
00039 # Revision 1.4 2006/01/31 17:46:06 aeby
00040 # filter(): avoid warnings about uninitialized values
00041 # analyze_sub(): added some more argument recognition patterns
00042 #
00043 # Revision 1.3 2006/01/31 16:53:52 aeby
00044 # added copyright info
00045 #
00046 #
00047 # =======================================================================
00048
00049 ## @file PerlFilter.pm
00050 # @brief Implementation of DoxyGen::PerlFilter.
00051
00052
00053 ## @class
00054 # Filter from perl syntax API docs to Doxygen-compatible syntax.
00055 # This class is meant to be used as a filter for the
00056 # <a href="http://www.doxygen.org/">Doxygen</a> documentation tool.
00057 package SBIA::BASIS::DoxyGen::PerlFilter;
00058
00059 use warnings;
00060 use strict;
00061 use base qw(SBIA::BASIS::DoxyGen::Filter);
00062 my $id = __PACKAGE__;
00063
00064 ## @method void filter($infh)
00065 # Do the filtering.
00066 # @param infh input filehandle, normally STDIN
00067 sub filter {
00068 my($self, $infile) = @_;
00069 open(my $infh, $infile);
00070 my $current_class = "";
00071 my $file = [];
00072 while( <$infh> ) {
00073 push( @$file, $_ );
00074 }
00075 $self->file_contents( $file );
00076 my $objcontext =
00077 grep( /^\s*use\s+base\s/, @$file )
00078 || grep( /\@ISA/, @$file )
00079 || grep( /^\s*bless/, @$file )
00080 || grep( /^\s*sub\s+new\s/, @$file )
00081 || grep( /\$self/, @$file );
00082
00083 push( @$file, "" ); # in order to have a delimiting empty line at EOF
00084 for( my $line=0; $line <= $#$file; ) {
00085 $_ = $file->[$line++];
00086 if (/^##\s*\@(\S+)\s*(.*)/) {
00087 my($command, $args) = ($1, $2);
00088 my @more;
00089 while ( $_ = $file->[$line++] ) {
00090 if (/^#\s?(.+)/s) {
00091 push @more, $1;
00092 } else {
00093 last;
00094 }
00095 }
00096 if ($command eq 'file') {
00097 $args ||= $infile;
00098 $self->start("\@$command $args");
00099 $self->more(@more);
00100 $self->end;
00101 } elsif ($command eq 'class') {
00102 $objcontext = 1;
00103 unless ($args) {
00104 ($args) = /package\s(.*);/;
00105 }
00106 if ($current_class) {
00107 $self->flush;
00108 $self->print("};\n");
00109 }
00110 $current_class = $args;
00111 $self->emit_class( $args, $line, [
00112 "\@$command $args",
00113 @more,
00114 "\@nosubgrouping"
00115 ] );
00116 } elsif ($command eq 'cmethod') {
00117 unless ($args) {
00118 ($args) = $self->analyze_sub( $line-1 );
00119 }
00120 $args = $self->munge_parameters($args);
00121 $self->push($self->protection($args).' Class Methods');
00122 $self->start("\@fn $args")->more(@more)->end;
00123 $self->print($args, ";\n");
00124 $self->pop;
00125 } elsif ($command eq 'fn') {
00126 unless ($args) {
00127 ($args) = $self->analyze_sub( $line-1 );
00128 }
00129 $args = $self->munge_parameters($args);
00130 $self->push($self->protection($args).' Functions');
00131 $self->start("\@fn $args")->more(@more)->end;
00132 $self->print($args, ";\n");
00133 $self->pop;
00134 } elsif ($command eq 'method') {
00135 unless ($args) {
00136 my( $name, @args );
00137 ($args, $name, @args) = $self->analyze_sub( $line-1 );
00138 $args =~ s/\$self,*\s*
00139 }
00140 $args = $self->munge_parameters($args);
00141 $self->push($self->protection($args).' Object Methods');
00142 $self->start("\@fn $args")->more(@more)->end;
00143 $self->print($args, ";\n");
00144 $self->pop;
00145 } elsif ($command eq 'enum') {
00146 $self->start("\@$command $args");
00147 $self->more(@more);
00148 $self->end;
00149 $self->print("$command $args;\n");
00150 } elsif ($command eq 'var') {
00151 $args =~ /([\w:]+)\s*([\w]+)\s*(.*)/ ;
00152 my $type = $1 ;
00153 my $name = $2 ;
00154 my $text = $3 ;
00155 $self->start( $text );
00156 $self->more( @more );
00157 $self->end();
00158 $self->print("$type $name;\n\n");
00159 } else {
00160 $self->start("\@$command $args");
00161 $self->more(@more);
00162 $self->end;
00163 }
00164 # We ate a line when we got the rest of the comment lines
00165 redo if defined $_;
00166 } elsif (/^use\s+([\w:]+)/) {
00167 my $inc = $1;
00168 $inc =~ s/::/\
00169 $self->print("#include \"$inc.pm\"\n");
00170 } elsif (/^package\s+([\w:]+)/) {
00171 if ($current_class) {
00172 $self->flush;
00173 $self->print("};\n");
00174 }
00175 next unless( $objcontext );
00176 $current_class = $1;
00177 $self->emit_class( $current_class, $line );
00178 } elsif (/^\s*sub\s+([\w:]+)/) {
00179 my( $proto, $name, @args ) = $self->analyze_sub( $line-1 );
00180 if( $current_class && @args && ($args[0] eq "\$self") ) {
00181 $self->push($self->protection($proto).' Object Methods');
00182 $proto =~ s/\$self,*\s*
00183 } elsif( $current_class
00184 && ((@args && ($args[0] eq "\$class")) || ($name eq "new")) ) {
00185 $self->push($self->protection($proto).' Class Methods');
00186 } else {
00187 $self->push($self->protection($proto).' Functions');
00188 }
00189 $proto = $self->munge_parameters($proto);
00190 $self->print($proto, ";\n");
00191 $self->pop;
00192 }
00193 }
00194 $self->flush();
00195 if ($current_class) {
00196 $self->print("};\n");
00197 }
00198 }
00199
00200
00201
00202 ## @method @ analyze_sub( int line )
00203 # analyzes a subroutine declaration starting at the given line. Tries
00204 # to determine whicht arguments it takes.
00205 #
00206 # @param line The line number at which the sub starts
00207 # @return A function prototype, the name of the function and a
00208 # list of arguments it takes
00209 sub analyze_sub {
00210 my( $self, $line ) = @_;
00211
00212 my $file = $self->file_contents();
00213 $file->[$line] =~ /sub\s+(.*)\{/;
00214 my $name = $1;
00215 my $proto;
00216 my @args;
00217 if( $name =~ /^(.*)\s*\((.*)\)/ ) {
00218 $name = $1;
00219 $proto = $2;
00220 }
00221 else {
00222 my $forward = 5;
00223 for( my $i=0; $forward && ($i+$line <= $#$file) && ! $proto; $i++ ) {
00224 $_ = $file->[$i+$line];
00225 if( /^\s*my\s*\((.*)\)\s*=\s*\@_/ ) {
00226 $proto = $1;
00227 }
00228 elsif( /^\s*(local|my)\s*([^\s]*)\s*=\s*shift\s*;/ ) {
00229 push( @args, $2 );
00230 }
00231 elsif( /^\s*(local|my)\s*([^\s]*)\s*=\s*\$_\[\s*(\d+)\s*]/ ) {
00232 $args[$3] = $2;
00233 }
00234 elsif( /shift\s*->\s*[a-z0-9_]+\(/ ) {
00235 # assuming anonymously used shifted value is $self
00236 push( @args, '$self' );
00237 }
00238 elsif( /^\s*\n/ || /^\s*#/ ) {
00239 ;
00240 }
00241 elsif( /}/ ) {
00242 $forward = 0;
00243 }
00244 else {
00245 $forward--;
00246 }
00247 }
00248 }
00249 if( $proto ) {
00250 $proto =~ s/\s+
00251 $proto =~ s/,/, /g;
00252 @args = split( ", ", $proto );
00253 }
00254
00255 $name =~ s/\s+$
00256 my $protection = "";
00257 if( substr( $name, 0, 1 ) eq "_" ) {
00258 $protection = "protected";
00259 }
00260 return( "$protection retval $name( ".join(", ", @args )." )", $name, @args );
00261 }
00262
00263
00264
00265 ## @method emit_class( string class, int line, arrayref doc )
00266 # Emit one class definition. If the doc parameter is defined,
00267 # emits the array as a comment just before the class definition,
00268 # otherwise, only the class definition is emitted.
00269 #
00270 # @param class the name of the class
00271 # @param line the current line number
00272 # @param doc (optional) an array with comment lines
00273 sub emit_class {
00274 my( $self, $class, $line, $doc ) = @_;
00275
00276 my(@current_isa, @current_include);
00277 my $file = $self->file_contents();
00278 while ($_ = $file->[$line++] ) {
00279 if (/^\s*(?:use base|\@ISA\s*=|\@${class}::ISA\s*=)\s+(.+);/) {
00280 @current_isa = eval $1;
00281 $file->[$line-1] = "\n";
00282 } elsif (/^use\s+([\w:]+)/) {
00283 my $inc = $1;
00284 $inc =~ s/::/\
00285 push @current_include, $inc;
00286 $file->[$line-1] = "\n";
00287 } elsif (/^package/) {
00288 last;
00289 }
00290 }
00291
00292 $self->print("#include \"$_.pm\"\n") foreach @current_include;
00293 $self->print("\n");
00294
00295 if( $doc ) {
00296 $self->start($doc->[0]);
00297 $self->more( @$doc[1 .. $#$doc] );
00298 $self->end();
00299 }
00300 $self->print("class $class");
00301
00302 if (@current_isa) {
00303 $self->print(":",
00304 join(", ", map {"public $_"} @current_isa) );
00305 }
00306 $self->print(" {\npublic:\n");
00307 }
00308
00309
00310
00311 ## @method arrayref file_contents( arrayref contents )
00312 # set/get an array containing the whole input file, each
00313 # line at one array index.
00314 #
00315 # @param contents (optional) file array ref
00316 # @return The file array ref
00317 sub file_contents {
00318 my( $self, $contents ) = @_;
00319
00320 $self->{"$id file"} = $contents if( defined $contents );
00321 return( $self->{"$id file"} );
00322 }
00323
00324
00325
00326 ## @method munge_parameters($args)
00327 # Munge the argument list. Because DoxyGen does not seem to handle $, @ and %
00328 # as argument types properly, we replace them with full length strings.
00329 #
00330 # @param args String specifying anything after a directive
00331 # @return Processed string.
00332 sub munge_parameters {
00333 my ($this, $args) = @_;
00334
00335 $args =~ s/\$\@/scalar_or_list /g;
00336 $args =~ s/\@\$/scalar_or_list /g;
00337 $args =~ s/\$/scalar /g;
00338 $args =~ s/\@/list /g;
00339 $args =~ s/\%/hash /g;
00340
00341 # my ($ret, $remainder) = ($args =~ /^\s*(\S+)(.+)/);
00342 # if ($ret) {
00343 # if ($ret eq '$') {
00344 # $ret = 'scalar';
00345 # } elsif ($ret eq '@') {
00346 # $ret = 'list';
00347 # } elsif ($ret eq '$@') {
00348 # $ret = 'scalar_or_list';
00349 # } elsif ($ret eq '@$') {
00350 # $ret = 'list_or_scalar';
00351 # }
00352 #
00353 # $args = "$ret$remainder";
00354 # }
00355
00356 return $args;
00357 }
00358
00359
00360 1;