package SrcParser; # This file is part of the build tools for Win32::GUI # It encapsulates a set of functions to parse and retrieve # documentation from within the source files. # # Author: Robert May , rmay@popeslane.clara.co.uk, 20 June 2005 # $Id: SrcParser.pm,v 1.5 2006/03/16 23:14:31 robertemay Exp $ use strict; use warnings; our $VERSION = "0.01"; our $DEBUG = 0; # parse(@files) # parses each file passed and "fixes" alternates that are found - see fix_alternates() # return 1 on success sub parse { for my $file (@_) { parse_file($file); } fix_alternates(); return 1; } # parse_file($file) # parses the file, performing rudimentary error checking, and adding any documentation # found to the stored data. # dies on errors to force the documentation to be fixed # returns 1 on success. my %PACKAGES; my %EVENTS; sub parse_file { my($file) = @_; print STDERR "Parsing file: '$file'\n" if $DEBUG; my $package; open(my $FILE, "<$file") or die "Can't open $file: $!"; while(<$FILE>) { # Look for packages if(/\(\@\)PACKAGE:\s*(.*\S)\s*$/) { $package = $1; print STDERR "Found package: '$package'\n" if $DEBUG; # initialise packages structure if we haven't already seen the package if(not exists $PACKAGES{$package}) { $PACKAGES{$package} = { files => [], methods => {}, abstract => '', description => '', }; } # store the filename and linenumber where we found this package definition push @{$PACKAGES{$package}->{files}}, $file . "[$.]"; # extract the package abstract and description # The abstract is on the line immediately following the package definition; # any remaining lines are package description; We need to be careful, as # packages can be defined in multiple places. Authors should take care to # only document the package once. If there are multiple options, then # GUI.pm should be the preferred location my $abstract = <$FILE>; # look at the next line if($abstract =~ s/^\s*(#|\*(\/)?)+\s*//) { # if it looks like documentation, then store the abstact and description if($abstract) { die "Package $package (${file} [$.]) found a second abstract" if length $PACKAGES{$package}->{abstract} > 0; $PACKAGES{$package}->{abstract} = $abstract; # find the description: while(<$FILE>) { if( s/^\s*(#|\*(\/)?)+\s?// ) { # if it looks like documentation, then store the description $PACKAGES{$package}->{description} .= ($_ eq '' ? "\n" : $_); } else { last; } } } } } # Look for methods elsif(/\(\@\)METHOD:\s*(.*\S)\s*$/) { my $method = $1; $method =~ s/\((.*)\)//; # strip the prototype my $methodprototype = $1; # and store it $method =~ s/\s+.*//; # strip anything after the first space - copes with "new Win32::GUI::Thing(args) my $methoddescr = ''; my @alternates; # We have an error if there's no current package die "Method $method (${file} [$.]) not in a package" if not defined $package; print STDERR "Found method: '${package}::${method}'\n\tPrototype: $methodprototype\n" if $DEBUG; # We have an error if the method has already been defined die "Method $method (${file} [$.]) already defined in package $package" if exists $PACKAGES{$package}->{methods}->{$method}; # Look at the method description while(<$FILE>) { if(/\(\@\)METHOD:\s*(.*\S)\s*$/) { my $alternate = $1; $alternate =~ s/\(.*\)//; # remove prototype $alternate =~ s/\s+.*//; push @alternates, $alternate; print STDERR "\tFound alternate method in package $package: '${alternate}'\n" if $DEBUG; } elsif( s/^\s*(#|\*(\/)?)+\s?// ) { $methoddescr .= ($_ eq '' ? "\n" : $_); } else { # store the method details: $PACKAGES{$package}->{methods}->{$method} = { prototype => $methodprototype, description => $methoddescr, alternates => \@alternates, }; last; } } } #Look for events elsif(/\(\@\)EVENT:\s*(.*\S)\s*$/) { my $event = $1; $event =~ s/\((.*)\)//; my $eventprototype = $1; $event =~ s/\s+.*//; my $eventdescr = ''; my @packages; print STDERR "Found event: '$event'\n\tPrototype: $eventprototype\n" if $DEBUG; while(<$FILE>) { if(/\(\@\)APPLIES_TO:\s*(.*\S)\s*$/) { my $applies = $1; @packages = split(/\s*,\s*/, $applies); print STDERR "\tApplies to: $applies\n" if $DEBUG; } elsif( s/^\s*(#|\*(\/)?)+\s?// ) { $eventdescr .= ($_ eq '' ? "\n" : $_); } else { # Store the event information if(scalar @packages == 0) { die "Event $event ($file) found that applies to no packages"; } # store the event against each package it applies to else { for my $pack (@packages) { $pack = "Win32::GUI::" . $pack unless $pack eq '*'; # The same event has multiple legitimate definitions in different packages # for the same package: # for example, Terminate() is described in both Window.xs and MDI.xs, # applying to Win32::GUI::Window package in each case. This is nasty to # document, but this is my best attempt: # - if the defining package and applies to package are the same store under # the name of the event only. # - if they are not, append the defining package to the hash key so that there # is no collision. my $frompackage = defined $package ? $package : $pack; my $tmpevent = $event; if ($frompackage ne $pack) { $tmpevent .= " ($frompackage)"; } # it's an error if we've already seen the event die "Event $event (${file} [$.]) alredy defined in package($pack)" if exists $EVENTS{$pack}->{$tmpevent}; # store the event info $EVENTS{$pack}->{$tmpevent} = { name => $event, prototype => $eventprototype, description => $eventdescr, file => $file . "[$.]", }; } } last; } } } } close($FILE); return 1; } # get_package_list() # returns a sorted list of all the packages sub get_package_list { return sort { uc $a cmp uc $b } keys %PACKAGES; } # get_package_abstract(package) # returns the abstract for a package sub get_package_abstract { my $package = shift; return $PACKAGES{$package}->{abstract}; } # get_package_description(package) # returns the description for a package sub get_package_description { my $package = shift; return $PACKAGES{$package}->{description}; } # get_package_method_list(package) # returns a sorted list of all the methods in a package sub get_package_method_list { my $package = shift; return sort newfirst keys %{$PACKAGES{$package}->{methods}}; } # helper to sort methods: new method first, then alpha sub newfirst { return ($a =~ /^new/) ? -1 : ($b =~ /^new/) ? 1 : uc($a) cmp uc($b); } # get_package_method_prototype(package, method) # returns the prototype of a method in a package sub get_package_method_prototype { my $package = shift; my $method = shift; return $PACKAGES{$package}->{methods}->{$method}->{prototype}; } # get_package_method_description(package, method) # returns the description of a method in a package sub get_package_method_description { my $package = shift; my $method = shift; return $PACKAGES{$package}->{methods}->{$method}->{description}; } # get_common_events_list() # returns a sorted list of all the global events sub get_common_event_list { return get_package_event_list('*'); } # get_package_events_list(package) # returns a sorted list of all the events associated with a package sub get_package_event_list { my $package = shift; return sort { lc $a cmp lc $b } keys %{$EVENTS{$package}}; } # get_common_event_name(event) # returns the name for the given common event sub get_common_event_name { my $event = shift; return get_package_event_name('*', $event); } # get_package_event_name(package, event) # returns the name of a given event in a package sub get_package_event_name { my $package = shift; my $event = shift; return $EVENTS{$package}->{$event}->{name}; } # get_common_event_prototype(event) # returns the prototype for the given common event sub get_common_event_prototype { my $event = shift; return get_package_event_prototype('*', $event); } # get_package_event_prototype(package, event) # returns the prototype of a given event in a package sub get_package_event_prototype { my $package = shift; my $event = shift; return $EVENTS{$package}->{$event}->{prototype}; } # get_common_event_description(event) # returns the raw description for the common event sub get_common_event_description { my $event = shift; return get_package_event_description('*', $event); } # get_package_event_description(package, event) # return the raw description for an event in a package sub get_package_event_description { my $package = shift; my $event = shift; return $EVENTS{$package}->{$event}->{description}; } # fix_alternates() # moves the alternate methods into the correct location, and adds text for them # - if alternate is in same package, add it with same prototype # and description 'See thismethod()' # - if package is different, add it with same prototype and description sub fix_alternates { for my $package (keys %PACKAGES) { for my $method (keys %{$PACKAGES{$package}->{methods}}) { my $alternates = $PACKAGES{$package}->{methods}->{$method}->{alternates}; my ($altpack, $altproto, $altdesc); for my $altmethod (@$alternates) { if ($altmethod !~ /^Win32::GUI::/) { $altpack = $package; $altproto = $PACKAGES{$package}->{methods}->{$method}->{prototype}; $altdesc = "See $method()"; } else { ($altpack = $altmethod) =~ s/(.*::)/$1/; $altproto = $PACKAGES{$package}->{methods}->{$method}->{prototype}; $altdesc = $PACKAGES{$package}->{methods}->{$method}->{description} . "\n\n See also ${package}::${method}()."; } die "alternate method ${altpack}::${altmethod} already defined." if exists $PACKAGES{$altpack}->{methods}->{$altmethod}; # store away the details: $PACKAGES{$altpack}->{methods}->{$altmethod} = { prototype => $altproto, description => $altdesc, }; } } } return 1; } 1; # end of SrcParser