#! /usr/bin/perl -w
#
# Generate API documentation. See documentation/documentation.sgml for details.
#
# Copyright (C) 2000 Mike McCormack
# Copyright (C) 2003 Jon Griffiths
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# TODO
# Consolidate A+W pairs together, and only write one doc, without the suffix
# Implement automatic docs fo structs/defines in headers
# SGML gurus - feel free to smarten up the SGML.
# Add any other relevant information for the dll - imports etc
# Should we have a special output mode for WineHQ?
use strict;
use bytes;
# Function flags. most of these come from the spec flags
my $FLAG_DOCUMENTED = 1;
my $FLAG_NONAME = 2;
my $FLAG_I386 = 4;
my $FLAG_REGISTER = 8;
my $FLAG_APAIR = 16; # The A version of a matching W function
my $FLAG_WPAIR = 32; # The W version of a matching A function
my $FLAG_64PAIR = 64; # The 64 bit version of a matching 32 bit function
# Options
my $opt_output_directory = "man3w"; # All default options are for nroff (man pages)
my $opt_manual_section = "3w";
my $opt_source_dir = "";
my $opt_wine_root_dir = "";
my $opt_output_format = ""; # '' = nroff, 'h' = html, 's' = sgml
my $opt_output_empty = 0; # Non-zero = Create 'empty' comments (for every implemented function)
my $opt_fussy = 1; # Non-zero = Create only if we have a RETURNS section
my $opt_verbose = 0; # >0 = verbosity. Can be given multiple times (for debugging)
my @opt_header_file_list = ();
my @opt_spec_file_list = ();
my @opt_source_file_list = ();
# All the collected details about all the .spec files being processed
my %spec_files;
# All the collected details about all the source files being processed
my %source_files;
# All documented functions that are to be placed in the index
my @index_entries_list = ();
# useful globals
my $pwd = `pwd`."/";
$pwd =~ s/\n//;
my @datetime = localtime;
my @months = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
my $year = $datetime[5] + 1900;
my $date = "$months[$datetime[4]] $year";
sub output_api_comment($);
sub output_api_footer($);
sub output_api_header($);
sub output_api_name($);
sub output_api_synopsis($);
sub output_close_api_file();
sub output_comment($);
sub output_html_index_files();
sub output_html_stylesheet();
sub output_open_api_file($);
sub output_sgml_dll_file($);
sub output_sgml_master_file($);
sub output_spec($);
sub process_comment($);
sub process_extra_comment($);
# Generate the list of exported entries for the dll
sub process_spec_file($)
{
my $spec_name = shift;
my $dll_name = $spec_name;
$dll_name =~ s/\..*//; # Strip the file extension
my $uc_dll_name = uc $dll_name;
my $spec_details =
{
NAME => $spec_name,
DLL_NAME => $dll_name,
NUM_EXPORTS => 0,
NUM_STUBS => 0,
NUM_FUNCS => 0,
NUM_FORWARDS => 0,
NUM_VARS => 0,
NUM_DOCS => 0,
CONTRIBUTORS => [ ],
SOURCES => [ ],
DESCRIPTION => [ ],
EXPORTS => [ ],
EXPORTED_NAMES => { },
IMPLEMENTATION_NAMES => { },
EXTRA_COMMENTS => [ ],
CURRENT_EXTRA => [ ] ,
};
if ($opt_verbose > 0)
{
print "Processing ".$spec_name."\n";
}
# We allow opening to fail just to cater for the peculiarities of
# the Wine build system. This doesn't hurt, in any case
open(SPEC_FILE, "<$spec_name")
|| (($opt_source_dir ne "")
&& open(SPEC_FILE, "<$opt_source_dir/$spec_name"))
|| return;
while()
{
s/^\s+//; # Strip leading space
s/\s+\n$/\n/; # Strip trailing space
s/\s+/ /g; # Strip multiple tabs & spaces to a single space
s/\s*#.*//; # Strip comments
s/\(.*\)/ /; # Strip arguments
s/\s+/ /g; # Strip multiple tabs & spaces to a single space (again)
s/\n$//; # Strip newline
my $flags = 0;
if( /\-noname/ )
{
$flags |= $FLAG_NONAME;
}
if( /\-i386/ )
{
$flags |= $FLAG_I386;
}
if( /\-register/ )
{
$flags |= $FLAG_REGISTER;
}
s/ \-[a-z0-9]+//g; # Strip flags
if( /^(([0-9]+)|@) / )
{
# This line contains an exported symbol
my ($ordinal, $call_convention, $exported_name, $implementation_name) = split(' ');
for ($call_convention)
{
/^(cdecl|stdcall|varargs|pascal)$/
&& do { $spec_details->{NUM_FUNCS}++; last; };
/^(variable|equate)$/
&& do { $spec_details->{NUM_VARS}++; last; };
/^(extern)$/
&& do { $spec_details->{NUM_FORWARDS}++; last; };
/^stub$/ && do { $spec_details->{NUM_STUBS}++; last; };
if ($opt_verbose > 0)
{
print "Warning: didn't recognise convention \'",$call_convention,"'\n";
}
last;
}
# Convert ordinal only names so we can find them later
if ($exported_name eq "@")
{
$exported_name = $uc_dll_name.'_'.$ordinal;
}
if (!defined($implementation_name))
{
$implementation_name = $exported_name;
}
if ($implementation_name eq "")
{
$implementation_name = $exported_name;
}
if ($implementation_name =~ /(.*?)\./)
{
$call_convention = "forward"; # Referencing a function from another dll
$spec_details->{NUM_FUNCS}--;
$spec_details->{NUM_FORWARDS}++;
}
# Add indices for the exported and implementation names
$spec_details->{EXPORTED_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
if ($implementation_name ne $exported_name)
{
$spec_details->{IMPLEMENTATION_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
}
# Add the exported entry
$spec_details->{NUM_EXPORTS}++;
my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $flags);
push (@{$spec_details->{EXPORTS}},[@export]);
}
}
close(SPEC_FILE);
# Add this .spec files details to the list of .spec files
$spec_files{$uc_dll_name} = [$spec_details];
}
# Read each source file, extract comments, and generate API documentation if appropriate
sub process_source_file($)
{
my $source_file = shift;
my $source_details =
{
CONTRIBUTORS => [ ],
DEBUG_CHANNEL => "",
};
my $comment =
{
FILE => $source_file,
COMMENT_NAME => "",
ALT_NAME => "",
DLL_NAME => "",
ORDINAL => "",
RETURNS => "",
PROTOTYPE => [],
TEXT => [],
};
my $parse_state = 0;
my $ignore_blank_lines = 1;
my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export
if ($opt_verbose > 0)
{
print "Processing ".$source_file."\n";
}
open(SOURCE_FILE,"<$source_file")
|| (($opt_source_dir ne "")
&& open(SOURCE_FILE,"<$opt_source_dir/$source_file"))
|| die "couldn't open ".$source_file."\n";
# Add this source file to the list of source files
$source_files{$source_file} = [$source_details];
while()
{
s/\n$//; # Strip newline
s/^\s+//; # Strip leading space
s/\s+$//; # Strip trailing space
if (! /^\*\|/ )
{
# Strip multiple tabs & spaces to a single space
s/\s+/ /g;
}
if ( / +Copyright *(\([Cc]\))*[0-9 \-\,\/]*([[:alpha:][:^ascii:] \.\-]+)/ )
{
# Extract a contributor to this file
my $contributor = $2;
$contributor =~ s/ *$//;
$contributor =~ s/^by //;
$contributor =~ s/\.$//;
$contributor =~ s/ (for .*)/ \($1\)/;
if ($contributor ne "")
{
if ($opt_verbose > 3)
{
print "Info: Found contributor:'".$contributor."'\n";
}
push (@{$source_details->{CONTRIBUTORS}},$contributor);
}
}
elsif ( /WINE_DEFAULT_DEBUG_CHANNEL\(([A-Za-z]*)\)/ )
{
# Extract the debug channel to use
if ($opt_verbose > 3)
{
print "Info: Found debug channel:'".$1."'\n";
}
$source_details->{DEBUG_CHANNEL} = $1;
}
if ($parse_state == 0) # Searching for a comment
{
if ( /^\/\**$/ )
{
# Found a comment start
$comment->{COMMENT_NAME} = "";
$comment->{ALT_NAME} = "";
$comment->{DLL_NAME} = "";
$comment->{ORDINAL} = "";
$comment->{RETURNS} = "";
$comment->{PROTOTYPE} = [];
$comment->{TEXT} = [];
$ignore_blank_lines = 1;
$extra_comment = 0;
$parse_state = 3;
}
}
elsif ($parse_state == 1) # Reading in a comment
{
if ( /^\**\// )
{
# Found the end of the comment
$parse_state = 2;
}
elsif ( s/^\*\|/\|/ )
{
# A line of comment not meant to be pre-processed
push (@{$comment->{TEXT}},$_); # Add the comment text
}
elsif ( s/^ *\** *// )
{
# A line of comment, starting with an asterisk
if ( /^[A-Z]+$/ || $_ eq "")
{
# This is a section start, so skip blank lines before and after it.
my $last_line = pop(@{$comment->{TEXT}});
if (defined($last_line) && $last_line ne "")
{
# Put it back
push (@{$comment->{TEXT}},$last_line);
}
if ( /^[A-Z]+$/ )
{
$ignore_blank_lines = 1;
}
else
{
$ignore_blank_lines = 0;
}
}
if ($ignore_blank_lines == 0 || $_ ne "")
{
push (@{$comment->{TEXT}},$_); # Add the comment text
}
}
else
{
# This isn't a well formatted comment: look for the next one
$parse_state = 0;
}
}
elsif ($parse_state == 2) # Finished reading in a comment
{
if ( /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ ||
/.*?\(/ )
{
# Comment is followed by a function definition
$parse_state = 4; # Fall through to read prototype
}
else
{
# Allow cpp directives and blank lines between the comment and prototype
if ($extra_comment == 1)
{
# An extra comment not followed by a function definition
$parse_state = 5; # Fall through to process comment
}
elsif (!/^\#/ && !/^ *$/ && !/^__ASM_GLOBAL_FUNC/)
{
# This isn't a well formatted comment: look for the next one
if ($opt_verbose > 1)
{
print "Info: Function '",$comment->{COMMENT_NAME},"' not followed by prototype.\n";
}
$parse_state = 0;
}
}
}
elsif ($parse_state == 3) # Reading in the first line of a comment
{
s/^ *\** *//;
if ( /^([\@A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])\s*(.*)$/ )
{
# Found a correctly formed "ApiName (DLLNAME.Ordinal)" line.
if (defined ($7) && $7 ne "")
{
push (@{$comment->{TEXT}},$_); # Add the trailing comment text
}
$comment->{COMMENT_NAME} = $1;
$comment->{DLL_NAME} = uc $3;
$comment->{ORDINAL} = $4;
$comment->{DLL_NAME} =~ s/^KERNEL$/KRNL386/; # Too many of these to ignore, _old_ code
$parse_state = 1;
}
elsif ( /^([A-Za-z0-9_-]+) +\{([A-Za-z0-9_]+)\}$/ )
{
# Found a correctly formed "CommentTitle {DLLNAME}" line (extra documentation)
$comment->{COMMENT_NAME} = $1;
$comment->{DLL_NAME} = uc $2;
$comment->{ORDINAL} = "";
$extra_comment = 1;
$parse_state = 1;
}
else
{
# This isn't a well formatted comment: look for the next one
$parse_state = 0;
}
}
if ($parse_state == 4) # Reading in the function definition
{
push (@{$comment->{PROTOTYPE}},$_);
# Strip comments from the line before checking for ')'
my $stripped_line = $_;
$stripped_line =~ s/ *(\/\* *)(.*?)( *\*\/ *)//;
if ( $stripped_line =~ /\)/ )
{
# Strip a blank last line
my $last_line = pop(@{$comment->{TEXT}});
if (defined($last_line) && $last_line ne "")
{
# Put it back
push (@{$comment->{TEXT}},$last_line);
}
if ($opt_output_empty != 0 && @{$comment->{TEXT}} == 0)
{
# Create a 'not implemented' comment
@{$comment->{TEXT}} = ("fixme: This function has not yet been documented.");
}
$parse_state = 5;
}
}
if ($parse_state == 5) # Processing the comment
{
# Process it, if it has any text
if (@{$comment->{TEXT}} > 0)
{
if ($extra_comment == 1)
{
process_extra_comment($comment);
}
else
{
@{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
process_comment($comment);
}
}
elsif ($opt_verbose > 1 && $opt_output_empty == 0)
{
print "Info: Function '",$comment->{COMMENT_NAME},"' has no documentation.\n";
}
$parse_state = 0;
}
}
close(SOURCE_FILE);
}
# Standardise a comments text for consistency
sub process_comment_text($)
{
my $comment = shift;
my $in_params = 0;
my @tmp_list = ();
my $i = 0;
for (@{$comment->{TEXT}})
{
my $line = $_;
if ( /^\s*$/ || /^[A-Z]+$/ || /^-/ )
{
$in_params = 0;
}
if ( $in_params > 0 && !/\[/ && !/\]/ )
{
# Possibly a continuation of the parameter description
my $last_line = pop(@tmp_list);
if ( $last_line =~ /\[/ && $last_line =~ /\]/ )
{
$line = $last_line." ".$_;
}
else
{
$in_params = 0;
push (@tmp_list, $last_line);
}
}
if ( /^(PARAMS|MEMBERS)$/ )
{
$in_params = 1;
}
push (@tmp_list, $line);
}
@{$comment->{TEXT}} = @tmp_list;
for (@{$comment->{TEXT}})
{
if (! /^\|/ )
{
# Map I/O values. These come in too many formats to standardise now....
s/\[I\]|\[i\]|\[in\]|\[IN\]/\[In\] /g;
s/\[O\]|\[o\]|\[out\]|\[OUT\]/\[Out\]/g;
s/\[I\/O\]|\[I\,O\]|\[i\/o\]|\[in\/out\]|\[IN\/OUT\]/\[In\/Out\]/g;
# TRUE/FALSE/NULL are defines, capitilise them
s/True|true/TRUE/g;
s/False|false/FALSE/g;
s/Null|null/NULL/g;
# Preferred capitalisations
s/ wine| WINE/ Wine/g;
s/ API | api / Api /g;
s/DLL|Dll/dll /g;
s/ URL | url / Url /g;
s/WIN16|win16/Win16/g;
s/WIN32|win32/Win32/g;
s/WIN64|win64/Win64/g;
s/ ID | id / Id /g;
# Grammar
s/([a-z])\.([A-Z])/$1\. $2/g; # Space after full stop
s/ \:/\:/g; # Colons to the left
s/ \;/\;/g; # Semi-colons too
# Common idioms
s/^See ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to A version from W
s/^Unicode version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Ditto
s/^64\-bit version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to 32 bit version from 64
s/^PARAMETERS$/PARAMS/; # Name of parameter section should be 'PARAMS'
# Trademarks
s/( |\.)(M\$|MS|Microsoft|microsoft|micro\$oft|Micro\$oft)( |\.)/$1Microsoft\(tm\)$3/g;
s/( |\.)(Windows|windows|windoze|winblows)( |\.)/$1Windows\(tm\)$3/g;
s/( |\.)(DOS|dos|msdos)( |\.)/$1MS-DOS\(tm\)$3/g;
s/( |\.)(UNIX|unix)( |\.)/$1Unix\(tm\)$3/g;
s/( |\.)(LINIX|linux)( |\.)/$1Linux\(tm\)$3/g;
# Abbreviations
s/( char )/ character /g;
s/( chars )/ characters /g;
s/( info )/ information /g;
s/( app )/ application /g;
s/( apps )/ applications /g;
s/( exe )/ executable /g;
s/( ptr )/ pointer /g;
s/( obj )/ object /g;
s/( err )/ error /g;
s/( bool )/ boolean /g;
s/( no\. )/ number /g;
s/( No\. )/ Number /g;
# Punctuation
if ( /\[I|\[O/ && ! /\.$/ )
{
$_ = $_."."; # Always have a full stop at the end of parameter desc.
}
elsif ($i > 0 && /^[A-Z]*$/ &&
!(@{$comment->{TEXT}}[$i-1] =~ /\.$/) &&
!(@{$comment->{TEXT}}[$i-1] =~ /\:$/))
{
if (!(@{$comment->{TEXT}}[$i-1] =~ /^[A-Z]*$/))
{
# Paragraphs always end with a full stop
@{$comment->{TEXT}}[$i-1] = @{$comment->{TEXT}}[$i-1].".";
}
}
}
$i++;
}
}
# Standardise our comment and output it if it is suitable.
sub process_comment($)
{
my $comment = shift;
# Don't process this comment if the function isn't exported
my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
if (!defined($spec_details))
{
if ($opt_verbose > 2)
{
print "Warning: Function '".$comment->{COMMENT_NAME}."' belongs to '".
$comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
}
return;
}
if ($comment->{COMMENT_NAME} eq "@")
{
my $found = 0;
# Find the name from the .spec file
for (@{$spec_details->{EXPORTS}})
{
if (@$_[0] eq $comment->{ORDINAL})
{
$comment->{COMMENT_NAME} = @$_[2];
$found = 1;
}
}
if ($found == 0)
{
# Create an implementation name
$comment->{COMMENT_NAME} = $comment->{DLL_NAME}."_".$comment->{ORDINAL};
}
}
my $exported_names = $spec_details->{EXPORTED_NAMES};
my $export_index = $exported_names->{$comment->{COMMENT_NAME}};
my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES};
if (!defined($export_index))
{
# Perhaps the comment uses the implementation name?
$export_index = $implementation_names->{$comment->{COMMENT_NAME}};
}
if (!defined($export_index))
{
# This function doesn't appear to be exported. hmm.
if ($opt_verbose > 2)
{
print "Warning: Function '".$comment->{COMMENT_NAME}."' claims to belong to '".
$comment->{DLL_NAME}."' but is not exported by it: not processing it.\n";
}
return;
}
# When the function is exported twice we have the second name below the first
# (you see this a lot in ntdll, but also in some other places).
my $first_line = ${@{$comment->{TEXT}}}[1];
if ( $first_line =~ /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
{
# Found a second name - mark it as documented
my $alt_index = $exported_names->{$1};
if (defined($alt_index))
{
if ($opt_verbose > 2)
{
print "Info: Found alternate name '",$1,"\n";
}
my $alt_export = @{$spec_details->{EXPORTS}}[$alt_index];
@$alt_export[4] |= $FLAG_DOCUMENTED;
$spec_details->{NUM_DOCS}++;
${@{$comment->{TEXT}}}[1] = "";
}
}
if (@{$spec_details->{CURRENT_EXTRA}})
{
# We have an extra comment that might be related to this one
my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
my $current_name = $current_comment->{COMMENT_NAME};
if ($comment->{COMMENT_NAME} =~ /^$current_name/ && $comment->{COMMENT_NAME} ne $current_name)
{
if ($opt_verbose > 2)
{
print "Linking ",$comment->{COMMENT_NAME}," to $current_name\n";
}
# Add a reference to this comment to our extra comment
push (@{$current_comment->{TEXT}}, $comment->{COMMENT_NAME}."()","");
}
}
# We want our docs generated using the implementation name, so they are unique
my $export = @{$spec_details->{EXPORTS}}[$export_index];
$comment->{COMMENT_NAME} = @$export[3];
$comment->{ALT_NAME} = @$export[2];
# Mark the function as documented
$spec_details->{NUM_DOCS}++;
@$export[4] |= $FLAG_DOCUMENTED;
# This file is used by the DLL - Make sure we get our contributors right
push (@{$spec_details->{SOURCES}},$comment->{FILE});
# If we have parameter comments in the prototype, extract them
my @parameter_comments;
for (@{$comment->{PROTOTYPE}})
{
s/ *\, */\,/g; # Strip spaces from around commas
if ( s/ *(\/\* *)(.*?)( *\*\/ *)// ) # Strip out comment
{
my $parameter_comment = $2;
if (!$parameter_comment =~ /^\[/ )
{
# Add [IO] markers so we format the comment correctly
$parameter_comment = "[fixme] ".$parameter_comment;
}
if ( /( |\*)([A-Za-z_]{1}[A-Za-z_0-9]*)(\,|\))/ )
{
# Add the parameter name
$parameter_comment = $2." ".$parameter_comment;
}
push (@parameter_comments, $parameter_comment);
}
}
# If we extracted any prototype comments, add them to the comment text.
if (@parameter_comments)
{
@parameter_comments = ("PARAMS", @parameter_comments);
my @new_comment = ();
my $inserted_params = 0;
for (@{$comment->{TEXT}})
{
if ( $inserted_params == 0 && /^[A-Z]+$/ )
{
# Found a section header, so this is where we insert
push (@new_comment, @parameter_comments);
$inserted_params = 1;
}
push (@new_comment, $_);
}
if ($inserted_params == 0)
{
# Add them to the end
push (@new_comment, @parameter_comments);
}
$comment->{TEXT} = [@new_comment];
}
if ($opt_fussy == 1 && $opt_output_empty == 0)
{
# Reject any comment that doesn't have a description or a RETURNS section.
# This is the default for now, 'coz many comments aren't suitable.
my $found_returns = 0;
my $found_description_text = 0;
my $in_description = 0;
for (@{$comment->{TEXT}})
{
if ( /^RETURNS$/ )
{
$found_returns = 1;
$in_description = 0;
}
elsif ( /^DESCRIPTION$/ )
{
$in_description = 1;
}
elsif ($in_description == 1)
{
if ( !/^[A-Z]+$/ )
{
# Don't reject comments that refer to another doc (e.g. A/W)
if ( /^See ([A-Za-z0-9_]+)\.$/ )
{
if ($comment->{COMMENT_NAME} =~ /W$/ )
{
# This is probably a Unicode version of an Ascii function.
# Create the Ascii name and see if its been documented
my $ascii_name = $comment->{COMMENT_NAME};
$ascii_name =~ s/W$/A/;
my $ascii_export_index = $exported_names->{$ascii_name};
if (!defined($ascii_export_index))
{
$ascii_export_index = $implementation_names->{$ascii_name};
}
if (!defined($ascii_export_index))
{
if ($opt_verbose > 2)
{
print "Warning: Function '".$comment->{COMMENT_NAME}."' is not an A/W pair.\n";
}
}
else
{
my $ascii_export = @{$spec_details->{EXPORTS}}[$ascii_export_index];
if (@$ascii_export[4] & $FLAG_DOCUMENTED)
{
# Flag these functions as an A/W pair
@$ascii_export[4] |= $FLAG_APAIR;
@$export[4] |= $FLAG_WPAIR;
}
}
}
$found_returns = 1;
}
elsif ( /^Unicode version of ([A-Za-z0-9_]+)\.$/ )
{
@$export[4] |= $FLAG_WPAIR; # Explicitly marked as W version
$found_returns = 1;
}
elsif ( /^64\-bit version of ([A-Za-z0-9_]+)\.$/ )
{
@$export[4] |= $FLAG_64PAIR; # Explicitly marked as 64 bit version
$found_returns = 1;
}
$found_description_text = 1;
}
else
{
$in_description = 0;
}
}
}
if ($found_returns == 0 || $found_description_text == 0)
{
if ($opt_verbose > 2)
{
print "Info: Function '",$comment->{COMMENT_NAME},"' has no ",
"description and/or RETURNS section, skipping\n";
}
$spec_details->{NUM_DOCS}--;
@$export[4] &= ~$FLAG_DOCUMENTED;
return;
}
}
process_comment_text($comment);
# Strip the prototypes return value, call convention, name and brackets
# (This leaves it as a list of types and names, or empty for void functions)
my $prototype = join(" ", @{$comment->{PROTOTYPE}});
$prototype =~ s/ / /g;
if ( $prototype =~ /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ )
{
$prototype =~ s/^(.*?) (WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16) (.*?)\( *(.*)/$4/;
$comment->{RETURNS} = $1;
}
else
{
$prototype =~ s/^(.*?)([A-Za-z0-9_]+)\( *(.*)/$3/;
$comment->{RETURNS} = $1;
}
$prototype =~ s/ *\).*//; # Strip end bracket
$prototype =~ s/ *\* */\*/g; # Strip space around pointers
$prototype =~ s/ *\, */\,/g; # Strip space around commas
$prototype =~ s/^(void|VOID)$//; # If void, leave blank
$prototype =~ s/\*([A-Za-z_])/\* $1/g; # Separate pointers from parameter name
@{$comment->{PROTOTYPE}} = split ( /,/ ,$prototype);
# FIXME: If we have no parameters, make sure we have a PARAMS: None. section
# Find header file
my $h_file = "";
if (@$export[4] & $FLAG_NONAME)
{
$h_file = "Exported by ordinal only. Use GetProcAddress() to obtain a pointer to the function.";
}
else
{
if ($comment->{COMMENT_NAME} ne "")
{
my $tmp = "grep -s -l $comment->{COMMENT_NAME} @opt_header_file_list 2>/dev/null";
$tmp = `$tmp`;
my $exit_value = $? >> 8;
if ($exit_value == 0)
{
$tmp =~ s/\n.*//g;
if ($tmp ne "")
{
$h_file = `basename $tmp`;
}
}
}
elsif ($comment->{ALT_NAME} ne "")
{
my $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null";
$tmp = `$tmp`;
my $exit_value = $? >> 8;
if ($exit_value == 0)
{
$tmp =~ s/\n.*//g;
if ($tmp ne "")
{
$h_file = `basename $tmp`;
}
}
}
$h_file =~ s/^ *//;
$h_file =~ s/\n//;
if ($h_file eq "")
{
$h_file = "Not defined in a Wine header. The function is either undocumented, or missing from Wine."
}
else
{
$h_file = "Defined in \"".$h_file."\".";
}
}
# Find source file
my $c_file = $comment->{FILE};
if ($opt_wine_root_dir ne "")
{
my $cfile = $pwd."/".$c_file; # Current dir + file
$cfile =~ s/(.+)(\/.*$)/$1/; # Strip the filename
$cfile = `cd $cfile && pwd`; # Strip any relative parts (e.g. "../../")
$cfile =~ s/\n//; # Strip newline
my $newfile = $c_file;
$newfile =~ s/(.+)(\/.*$)/$2/; # Strip all but the filename
$cfile = $cfile."/".$newfile; # Append filename to base path
$cfile =~ s/$opt_wine_root_dir//; # Get rid of the root directory
$cfile =~ s/\/\//\//g; # Remove any double slashes
$cfile =~ s/^\/+//; # Strip initial directory slash
$c_file = $cfile;
}
$c_file = "Implemented in \"".$c_file."\".";
# Add the implementation details
push (@{$comment->{TEXT}}, "IMPLEMENTATION","",$h_file,"",$c_file);
if (@$export[4] & $FLAG_I386)
{
push (@{$comment->{TEXT}}, "", "Available on x86 platforms only.");
}
if (@$export[4] & $FLAG_REGISTER)
{
push (@{$comment->{TEXT}}, "", "This function passes one or more arguments in registers. ",
"For more details, please read the source code.");
}
my $source_details = $source_files{$comment->{FILE}}[0];
if ($source_details->{DEBUG_CHANNEL} ne "")
{
push (@{$comment->{TEXT}}, "", "Debug channel \"".$source_details->{DEBUG_CHANNEL}."\".");
}
# Write out the documentation for the API
output_comment($comment)
}
# process our extra comment and output it if it is suitable.
sub process_extra_comment($)
{
my $comment = shift;
my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
if (!defined($spec_details))
{
if ($opt_verbose > 2)
{
print "Warning: Extra comment '".$comment->{COMMENT_NAME}."' belongs to '".
$comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
}
return;
}
# Check first to see if this is documentation for the DLL.
if ($comment->{COMMENT_NAME} eq $comment->{DLL_NAME})
{
if ($opt_verbose > 2)
{
print "Info: Found DLL documentation\n";
}
for (@{$comment->{TEXT}})
{
push (@{$spec_details->{DESCRIPTION}}, $_);
}
return;
}
# Add the comment to the DLL page as a link
push (@{$spec_details->{EXTRA_COMMENTS}},$comment->{COMMENT_NAME});
# If we have a prototype, process as a regular comment
if (@{$comment->{PROTOTYPE}})
{
$comment->{ORDINAL} = "@";
# Add an index for the comment name
$spec_details->{EXPORTED_NAMES}{$comment->{COMMENT_NAME}} = $spec_details->{NUM_EXPORTS};
# Add a fake exported entry
$spec_details->{NUM_EXPORTS}++;
my ($ordinal, $call_convention, $exported_name, $implementation_name, $documented) =
("@", "fake", $comment->{COMMENT_NAME}, $comment->{COMMENT_NAME}, 0);
my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
push (@{$spec_details->{EXPORTS}},[@export]);
@{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
process_comment($comment);
return;
}
if ($opt_verbose > 0)
{
print "Processing ",$comment->{COMMENT_NAME},"\n";
}
if (@{$spec_details->{CURRENT_EXTRA}})
{
my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
if ($opt_verbose > 0)
{
print "Processing old current: ",$current_comment->{COMMENT_NAME},"\n";
}
# Output the current comment
process_comment_text($current_comment);
output_open_api_file($current_comment->{COMMENT_NAME});
output_api_header($current_comment);
output_api_name($current_comment);
output_api_comment($current_comment);
output_api_footer($current_comment);
output_close_api_file();
}
if ($opt_verbose > 2)
{
print "Setting current to ",$comment->{COMMENT_NAME},"\n";
}
my $comment_copy =
{
FILE => $comment->{FILE},
COMMENT_NAME => $comment->{COMMENT_NAME},
ALT_NAME => $comment->{ALT_NAME},
DLL_NAME => $comment->{DLL_NAME},
ORDINAL => $comment->{ORDINAL},
RETURNS => $comment->{RETURNS},
PROTOTYPE => [],
TEXT => [],
};
for (@{$comment->{TEXT}})
{
push (@{$comment_copy->{TEXT}}, $_);
}
# Set this comment to be the current extra comment
@{$spec_details->{CURRENT_EXTRA}} = ($comment_copy);
}
# Write a standardised comment out in the appropriate format
sub output_comment($)
{
my $comment = shift;
if ($opt_verbose > 0)
{
print "Processing ",$comment->{COMMENT_NAME},"\n";
}
if ($opt_verbose > 4)
{
print "--PROTO--\n";
for (@{$comment->{PROTOTYPE}})
{
print "'".$_."'\n";
}
print "--COMMENT--\n";
for (@{$comment->{TEXT} })
{
print $_."\n";
}
}
output_open_api_file($comment->{COMMENT_NAME});
output_api_header($comment);
output_api_name($comment);
output_api_synopsis($comment);
output_api_comment($comment);
output_api_footer($comment);
output_close_api_file();
}
# Write out an index file for each .spec processed
sub process_index_files()
{
foreach my $spec_file (keys %spec_files)
{
my $spec_details = $spec_files{$spec_file}[0];
if (defined ($spec_details->{DLL_NAME}))
{
if (@{$spec_details->{CURRENT_EXTRA}})
{
# We have an unwritten extra comment, write it
my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
process_extra_comment($current_comment);
@{$spec_details->{CURRENT_EXTRA}} = ();
}
output_spec($spec_details);
}
}
}
# Write a spec files documentation out in the appropriate format
sub output_spec($)
{
my $spec_details = shift;
if ($opt_verbose > 2)
{
print "Writing:",$spec_details->{DLL_NAME},"\n";
}
# Use the comment output functions for consistency
my $comment =
{
FILE => $spec_details->{DLL_NAME},
COMMENT_NAME => $spec_details->{DLL_NAME}.".dll",
ALT_NAME => $spec_details->{DLL_NAME},
DLL_NAME => "",
ORDINAL => "",
RETURNS => "",
PROTOTYPE => [],
TEXT => [],
};
my $total_implemented = $spec_details->{NUM_FORWARDS} + $spec_details->{NUM_VARS} +
$spec_details->{NUM_FUNCS};
my $percent_implemented = 0;
if ($total_implemented)
{
$percent_implemented = $total_implemented /
($total_implemented + $spec_details->{NUM_STUBS}) * 100;
}
$percent_implemented = int($percent_implemented);
my $percent_documented = 0;
if ($spec_details->{NUM_DOCS})
{
# Treat forwards and data as documented funcs for statistics
$percent_documented = $spec_details->{NUM_DOCS} / $spec_details->{NUM_FUNCS} * 100;
$percent_documented = int($percent_documented);
}
# Make a list of the contributors to this DLL. Do this only for the source
# files that make up the DLL, because some directories specify multiple dlls.
my @contributors;
for (@{$spec_details->{SOURCES}})
{
my $source_details = $source_files{$_}[0];
for (@{$source_details->{CONTRIBUTORS}})
{
push (@contributors, $_);
}
}
my %saw;
@contributors = grep(!$saw{$_}++, @contributors); # remove dups, from perlfaq4 manpage
@contributors = sort @contributors;
# Remove duplicates and blanks
for(my $i=0; $i<@contributors; $i++)
{
if ($i > 0 && ($contributors[$i] =~ /$contributors[$i-1]/ || $contributors[$i-1] eq ""))
{
$contributors[$i-1] = $contributors[$i];
}
}
undef %saw;
@contributors = grep(!$saw{$_}++, @contributors);
if ($opt_verbose > 3)
{
print "Contributors:\n";
for (@contributors)
{
print "'".$_."'\n";
}
}
my $contribstring = join (", ", @contributors);
# Create the initial comment text
@{$comment->{TEXT}} = (
"NAME",
$comment->{COMMENT_NAME}
);
# Add the description, if we have one
if (@{$spec_details->{DESCRIPTION}})
{
push (@{$comment->{TEXT}}, "DESCRIPTION");
for (@{$spec_details->{DESCRIPTION}})
{
push (@{$comment->{TEXT}}, $_);
}
}
# Add the statistics and contributors
push (@{$comment->{TEXT}},
"STATISTICS",
"Forwards: ".$spec_details->{NUM_FORWARDS},
"Variables: ".$spec_details->{NUM_VARS},
"Stubs: ".$spec_details->{NUM_STUBS},
"Functions: ".$spec_details->{NUM_FUNCS},
"Exports-Total: ".$spec_details->{NUM_EXPORTS},
"Implemented-Total: ".$total_implemented." (".$percent_implemented."%)",
"Documented-Total: ".$spec_details->{NUM_DOCS}." (".$percent_documented."%)",
"CONTRIBUTORS",
"The following people hold copyrights on the source files comprising this dll:",
"",
$contribstring,
"Note: This list may not be complete.",
"For a complete listing, see the Files \"AUTHORS\" and \"Changelog\" in the Wine source tree.",
"",
);
if ($opt_output_format eq "h")
{
# Add the exports to the comment text
push (@{$comment->{TEXT}},"EXPORTS");
my $exports = $spec_details->{EXPORTS};
for (@$exports)
{
my $line = "";
# @$_ => ordinal, call convention, exported name, implementation name, flags;
if (@$_[1] eq "forward")
{
my $forward_dll = @$_[3];
$forward_dll =~ s/\.(.*)//;
$line = @$_[2]." (forward to ".$1."() in ".$forward_dll."())";
}
elsif (@$_[1] eq "extern")
{
$line = @$_[2]." (extern)";
}
elsif (@$_[1] eq "stub")
{
$line = @$_[2]." (stub)";
}
elsif (@$_[1] eq "fake")
{
# Don't add this function here, it gets listed with the extra documentation
if (!(@$_[4] & $FLAG_WPAIR))
{
# This function should be indexed
push (@index_entries_list, @$_[3].",".@$_[3]);
}
}
elsif (@$_[1] eq "equate" || @$_[1] eq "variable")
{
$line = @$_[2]." (data)";
}
else
{
# A function
if (@$_[4] & $FLAG_DOCUMENTED)
{
# Documented
$line = @$_[2]." (implemented as ".@$_[3]."())";
if (@$_[2] ne @$_[3])
{
$line = @$_[2]." (implemented as ".@$_[3]."())";
}
else
{
$line = @$_[2]."()";
}
if (!(@$_[4] & $FLAG_WPAIR))
{
# This function should be indexed
push (@index_entries_list, @$_[2].",".@$_[3]);
}
}
else
{
$line = @$_[2]." (not documented)";
}
}
if ($line ne "")
{
push (@{$comment->{TEXT}}, $line, "");
}
}
# Add links to the extra documentation
if (@{$spec_details->{EXTRA_COMMENTS}})
{
push (@{$comment->{TEXT}}, "SEE ALSO");
my %htmp;
@{$spec_details->{EXTRA_COMMENTS}} = grep(!$htmp{$_}++, @{$spec_details->{EXTRA_COMMENTS}});
for (@{$spec_details->{EXTRA_COMMENTS}})
{
push (@{$comment->{TEXT}}, $_."()", "");
}
}
}
# The dll entry should also be indexed
push (@index_entries_list, $spec_details->{DLL_NAME}.",".$spec_details->{DLL_NAME});
# Write out the document
output_open_api_file($spec_details->{DLL_NAME});
output_api_header($comment);
output_api_comment($comment);
output_api_footer($comment);
output_close_api_file();
# Add this dll to the database of dll names
my $output_file = $opt_output_directory."/dlls.db";
# Append the dllname to the output db of names
open(DLLDB,">>$output_file") || die "Couldn't create $output_file\n";
print DLLDB $spec_details->{DLL_NAME},"\n";
close(DLLDB);
if ($opt_output_format eq "s")
{
output_sgml_dll_file($spec_details);
return;
}
}
#
# OUTPUT FUNCTIONS
# ----------------
# Only these functions know anything about formatting for a specific
# output type. The functions above work only with plain text.
# This is to allow new types of output to be added easily.
# Open the api file
sub output_open_api_file($)
{
my $output_name = shift;
$output_name = $opt_output_directory."/".$output_name;
if ($opt_output_format eq "h")
{
$output_name = $output_name.".html";
}
elsif ($opt_output_format eq "s")
{
$output_name = $output_name.".sgml";
}
else
{
$output_name = $output_name.".".$opt_manual_section;
}
open(OUTPUT,">$output_name") || die "Couldn't create file '$output_name'\n";
}
# Close the api file
sub output_close_api_file()
{
close (OUTPUT);
}
# Output the api file header
sub output_api_header($)
{
my $comment = shift;
if ($opt_output_format eq "h")
{
print OUTPUT "\n";
print OUTPUT "\n";
print OUTPUT "\n\n";
print OUTPUT "\n";
print OUTPUT "\n";
print OUTPUT "{COMMENT_NAME}\">\n";
print OUTPUT "Wine API: $comment->{COMMENT_NAME}\n\n\n";
}
elsif ($opt_output_format eq "s")
{
print OUTPUT "\n",
"\n",
"$comment->{COMMENT_NAME}\n";
}
else
{
print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n".
".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"".
"Wine API\" \"Wine API\"\n";
}
}
sub output_api_footer($)
{
if ($opt_output_format eq "h")
{
print OUTPUT "