#!/usr/bin/perl -w ############################################################################### # # # Pseudocode.pl # # # ############################################################################### # # # Description: Renders Doxygen-like source code documentation, only # # focuses on the procedure INSIDE a function or method, # # rather than the calling API. # # # # Author: Mark Zieg # # # # Date: Nov 19, 2002 # # # # Usage: $ PseudocodeFile.pl < source.c > source.html # # # # Notes: Parses the following syntax: # # # # All lines must start with (optionally-whitespace-prefixed) # # slash-slash-bang-bang comment leaders. # # # # Commands recognized: # # # # @func, @funct # # Identifies beginning of new function # # # # @for ... @endfor # # encloses an indented for() block # # # # @while ... @endwhile # # encloses an indented while() block # # # # @if ... @else ... @endif # # encloses an indented if/then/else block # # # # @switch ... @case ... @endswitch # # encloses an indented switch/case block # # # # @warning, @kludge, @hack, @todo, @notyet, @notdone # # synonymous "alert" comments that should be # # called out in an "alarming" fashion # # # # @note, @question, @huh, @review, @wtf # # synonymous "huh?" comments that represent # # good fodder for peer review # # # # @block...@newblock...@endblock # # specifies major sections within a function # # (ie, something that should probably be it's own # # function! :-) # # # # @crit, @key # # specifies a comment that is so critical or # # important that the reader should not be allowed # # to miss it # # # # @return, @continue # # highlights control points # # # # Instead of the JavaDoc "@Cmd" style, commands can also be # # in the Qt \Cmd style. # # # ############################################################################### use strict; # use Data::Dumper; my $rh_Functions = {}; my $BlockCount = 0; my $BLOCK_BACKGROUND = '#e7e7e7'; my $TITLE_BACKGROUND = '#ddddff'; my $FONT = ''; my @BLOCK_COLORS = qw( e7e7e7 ffeeee eeffee eeeeff eeffff ffeeff ffffee ); my $File = ""; MAIN: { #! @func Main #! @if do we have any arguments? if( $#ARGV != -1 ) { #! yes, we have arguments -- print usage and die. Usage(); exit( 1 ); } #! endif my $rh_Function; #! @while foreach line from stdin while( my $Line = ) { #! hack out a few Doxygen bits, if found if( $Line =~ /[@\\]file\s+(\S+)/i ) { $File = $1; next; } #! @if is this a pseudocode line? if( $Line =~ /^(.*)(\/\/!!|\#!{1,2})\s*(.*?)\s*$/ ) { #! yes, this is a metadata line my $Code = $1; my $Data = $3; #! parse line my $Cmd; my $Opts; #! @if is this a new command? if( $Data =~ /^[@\\]([^:\t ]+):?\s*(.*)$/ ) { #! yes, this is a new command. #! parse out command and options $Cmd = $1; $Opts = $2; #! cleanse comment for HTML display $Opts =~ s/>/>/g; $Opts =~ s/ $rh_Function, ); #! @switch process this command if( $Cmd =~ /^funct?$/i ) { #! @case func. #! print last (finished) function SaveFunction( $rh_Function ) if $rh_Function; #! start a new function $rh_Function = NewFunction( $Opts ); } elsif( $Cmd =~ /^(return|continue|break)$/i ) { #! @case return/continue/break StartComment( FUNCTION => $rh_Function, TEXT => "$Cmd $Opts", ); } elsif( $Cmd =~ /^block|h(\d)$/i ) { #! @case block $BlockCount++; my $HeaderLevel = $1 || 1; StartComment( FUNCTION => $rh_Function, TEXT => "$Opts", BLOCK_TITLE => $HeaderLevel, ); } elsif( $Cmd =~ /^(warning|kludge|hack|todo|notyet|notdone|cheat|truth|bug)$/i ) { #! @case warning/todo/notyet/... StartComment( FUNCTION => $rh_Function, TEXT => "$Cmd $Opts", COLOR => "red", ); } elsif( $Cmd =~ /^(huh|what|note|question|wtf|review)$/i ) { #! @case note/question/review/... StartComment( FUNCTION => $rh_Function, TEXT => "$Cmd $Opts", COLOR => "green", ); } elsif( $Cmd =~ /^(crit|key|answer)$/i ) { #! @case crit/key StartComment( FUNCTION => $rh_Function, TEXT => "$Cmd $Opts", COLOR => "blue", ); } elsif( $Cmd =~ /^(commented)$/i ) { #! @case commented StartComment( FUNCTION => $rh_Function, TEXT => $Opts, COLOR => "#cccccc", ); } elsif( $Cmd =~ /^(for|foreach|if|while)$/i ) { #! @case for/if/while/... StartComment( FUNCTION => $rh_Function, TEXT => "$Opts", ITALICS => 1, ); Indent( FUNCTION => $rh_Function, ); } elsif( $Cmd =~ /^(switch)$/i ) { #! @case switch StartComment( FUNCTION => $rh_Function, TEXT => "$Opts", ITALICS => 1, ); Indent( FUNCTION => $rh_Function, ); Indent( FUNCTION => $rh_Function, ); } elsif( $Cmd =~ /^(else)$/i ) { #! @case else Unindent( FUNCTION => $rh_Function, ); StartComment( FUNCTION => $rh_Function, TEXT => "else", ITALICS => 1, AUTOFINISH => 1, ); Indent( FUNCTION => $rh_Function, ); StartComment( FUNCTION => $rh_Function, TEXT => $Opts, ) } elsif( $Cmd =~ /^(case)$/i ) { #! @case case Unindent( FUNCTION => $rh_Function, ); StartComment( FUNCTION => $rh_Function, TEXT => "case $Opts", ); Indent( FUNCTION => $rh_Function, ); } elsif( $Cmd =~ /^(endfor|endwhile|endif)$/i ) { #! @case endfor/endif/... Unindent( FUNCTION => $rh_Function, ); } elsif( $Cmd =~ /^(endswitch)$/i ) { #! @case endswitch Unindent( FUNCTION => $rh_Function, ); Unindent( FUNCTION => $rh_Function, ); } else { #! @case default #! display warning warn( "unknown command: $Cmd" ); StartComment( FUNCTION => $rh_Function, TEXT => "[Unknown cmd] $Cmd $Opts", COLOR => "red", ); } #! @endswitch } else { #! @else no, this is not a command line #! @if does this line have program code before the comment? if( $Code =~ /\S/ ) { #! yes, this line appears to have program code before the comment. #! assume we are to start a new comment. FinishComment( FUNCTION => $rh_Function, ); StartComment( FUNCTION => $rh_Function, TEXT => $Data, ); } else { #! @else no, this is presumably a continuation of the #! previous comment. #! add text to current comment ContinueComment( FUNCTION => $rh_Function, TEXT => $Data, ); } #! @endif } #! @endif } else { #! @else no, this is not a metadata line #! commit (completed) comment to the current function, if either exists FinishComment( FUNCTION => $rh_Function, ); } #! @endif } #! @endwhile #! close last function SaveFunction( $rh_Function ) if $rh_Function; #! @crit print everything to stdout PrintEverything(); #! @return all done! } sub StartComment { my %Args = @_; #! @func StartComment #! extract the current function my $rh_Function = $Args{FUNCTION}; #! @if is the current function already in the middle of a comment? if( $rh_Function->{CurrentComment} ) { #! yes, the current function is in the middle of a comment. #! finish the current comment. FinishComment( FUNCTION => $rh_Function, ); } #! @endif #! initialize the new comment to default values (let the caller override) my $rh_Comment = {}; $rh_Comment->{Text} = $Args{TEXT}; $rh_Comment->{Color} = $Args{COLOR}; $rh_Comment->{Indent} = $rh_Function->{Indent}; $rh_Comment->{BlockCount} = $BlockCount; $rh_Comment->{Bold} = $Args{BOLD} || 0; $rh_Comment->{BlockTitle} = $Args{BLOCK_TITLE}|| 0; $rh_Comment->{Italics} = $Args{ITALICS} || 0; #! make the new comment the "current" comment for the function $rh_Function->{CurrentComment} = $rh_Comment; #! @if should we close this comment now, immediately upon creation? if( $Args{AutoFinish} || $Args{TEXT} =~ /\.\s*$/ ) { #! yes, we should (either AutoFinish was specified or the comment ended in a period). #! finish the "current" comment for this function (which is the one we just made). FinishComment( FUNCTION => $rh_Function, ); } #! @endif #! @return all done! } sub Indent { my %Args = @_; my $rh_Function = $Args{FUNCTION}; $rh_Function->{Indent}++; } sub Unindent { my %Args = @_; my $rh_Function = $Args{FUNCTION}; $rh_Function->{Indent}--; } sub ContinueComment { my %Args = @_; #! @func ContinueComment my $rh_Function = $Args{FUNCTION}; my $Text = $Args{TEXT}; #! @if is this function already in the middle of a comment? if( $rh_Function->{CurrentComment} ) { #! yes, this function is already in the middle of a comment. #! get a handle to the current comment. my $rh_Comment = $rh_Function->{CurrentComment}; #! append the new text to it. $rh_Comment->{Text} .= " $Text"; } else { #! @else no, the current function is not already building a comment. #! so start a new one with this text. StartComment( FUNCTION => $rh_Function, TEXT => $Text, ); } #! @endif #! @return all done! } sub FinishComment { my %Args = @_; my $rh_Function = $Args{FUNCTION}; return unless $rh_Function; my $rh_Comment = $rh_Function->{CurrentComment}; if( $rh_Comment ) { push( @{$rh_Function->{Comments}}, $rh_Comment ); } $rh_Function->{CurrentComment} = 0; } sub NewFunction { my $FuncName = shift; my $rh_Function = {}; $rh_Function->{Comments} = []; $rh_Function->{FuncName} = $FuncName ? $FuncName : "undef"; $rh_Function->{Indent} = 0; $rh_Function->{CurrentComment} = 0; $BlockCount = 0; StartComment( FUNCTION => $rh_Function, TEXT => "start", AUTOFINISH => 1, ); return $rh_Function; } sub SaveFunction { my $rh_Function = shift; FinishComment( FUNCTION => $rh_Function, ); $rh_Functions->{$rh_Function->{FuncName}} = $rh_Function; } sub PrintEverything { #! @func PrintEverything # print STDERR Dumper( $rh_Functions ); ########################################################################### #! @block print header/TOC ########################################################################### #! print header print ""; print "$File " if $File; print "pseudocode\n"; if( $File ) { print "

$File

\n"; } else { print "

Contents

\n"; } print "
    \n"; #! print TOC foreach my $FuncName (sort keys %{$rh_Functions}) { print "
  • $FuncName()\n"; } #! finish TOC print "



\n"; ########################################################################### #! @block print function bodies ########################################################################### #! start function bodies print "$FONT\n"; #! @foreach iterate through each function foreach my $FuncName (sort keys %{$rh_Functions}) { #! start function my $rh_Function = $rh_Functions->{$FuncName}; print "\n"; print "\n"; print "\n"; print "\n"; print "
  $FuncName()
\n"; print "


\n"; print "

    \n"; #! @foreach iterate through each comment my $CurrentIndent = 0; foreach my $rh_Comment (@{$rh_Function->{Comments}}) { my $Indent = $rh_Comment->{Indent}; my @OpenTags; my @CloseTags; my $LiOpts = ""; my $LiPrefix = ""; my $LiSuffix = ""; #! catch up to this comment's correct indentation level #! @warning it would be better if some validation appeared here while( $Indent > $CurrentIndent ) { print "
      \n"; $CurrentIndent++; } while( $Indent < $CurrentIndent ) { print "
    \n"; $CurrentIndent--; warn( "$FuncName: negative indent!" ) if( $CurrentIndent < 0 ); } #! format comment if( $rh_Comment->{BlockTitle} ) { # this must go on the outside! # $LiSuffix = "\"\""; # push( @OpenTags, "" ); # push( @CloseTags, "

    " ); my $Level = $rh_Comment->{BlockTitle} + 1; push( @OpenTags, "" ); push( @CloseTags, "" ); } if( $rh_Comment->{Color} ) { push( @OpenTags, "{Color}>" ); push( @CloseTags, "" ); } if( $rh_Comment->{BlockCount} ) { my $Color = $BLOCK_COLORS[ ( $rh_Comment->{BlockCount} - 1 ) % ( $#BLOCK_COLORS + 1 ) ]; #! @commented background colors disabled until reliable and attractive solution #! is found to handle colorized nested blocks. #$LiOpts = "STYLE=\"background-color:$Color\""; } if( $rh_Comment->{Bold} ) { push( @OpenTags, "" ); push( @CloseTags, "" ); } if( $rh_Comment->{Italics} ) { push( @OpenTags, "" ); push( @CloseTags, "" ); } #! render comment print $LiPrefix . "

  1. " . $LiSuffix . join( "", @OpenTags ) . $rh_Comment->{Text} . join( "", reverse @CloseTags ) . "\n"; } #! @endfor (comments) #! clean up any broken indents while( $CurrentIndent < 0 ) { warn( "$FuncName: negative indent!" ); $CurrentIndent++; } while( $CurrentIndent > 0 ) { #warn( "$FuncName: hanging indent!" ); print "
\n"; $CurrentIndent--; } #! finish function print "\n"; print "


\n"; } #! @endfor (functions) #! end body print "
\n"; } sub Usage { my $RevisionTag = '$Revision: 1.1 $'; my ($Revision) = ( $RevisionTag =~ /([0-9.]+)/ ); print "Pseudocode $Revision, by Mark Zieg\n"; print "\n"; print "usage: Pseudocode.pl < foo.c > foo.html\n"; print "\n"; print "Supported tags:\n"; print "\n"; print "function name:\n"; print " \@func, \@funct\n"; print "headings:\n"; print " \@block, \@h1..\@h5\n"; print "branching:\n"; print " \@return, \@continue, \@break\n"; print "problems:\n"; print " \@warning, \@kludge, \@hack, \@todo, \@notyet, \@notdone, \@cheat, \@truth, \@bug\n"; print "questions:\n"; print " \@huh, \@what, \@note, \@question, \@wtf, \@review\n"; print "importance:\n"; print " \@crit, \@key, \@answer\n"; print "historical:\n"; print " \@commented\n"; print "control structures:\n"; print " \@if, \@switch, \@while, \@for, \@foreach\n"; print " \@else, \@case\n"; print " \@endif, \@endswitch, \@endwhile, \@endfor\n"; print "\n"; print "Tags are added all the time...check the source for final reference!\n"; }