Framework for the doppler effect.
[wine] / tools / c2man.pl
1 #! /usr/bin/perl -w
2 #
3 # Generate API documentation. See documentation/documentation.sgml for details.
4 #
5 # Copyright (C) 2000 Mike McCormack
6 # Copyright (C) 2003 Jon Griffiths
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 #
22 # TODO
23 #  SGML gurus - feel free to smarten up the SGML.
24 #  Add any other relevant information for the dll - imports etc
25 #  Read .spec flags such as -i386,-noname and add to docs appropriately
26 #  Generate an alpabetical listing of every API call, with links
27 #  Should we have a special output mode for WineHQ?
28
29 use strict;
30
31 # Options
32 my $opt_output_directory = "man3w"; # All default options are for nroff (man pages)
33 my $opt_manual_section = "3w";
34 my $opt_wine_root_dir = "";
35 my $opt_output_format = "";         # '' = nroff, 'h' = html, 's' = sgml
36 my $opt_output_empty = 0;           # Non-zero = Create 'empty' comments (for every implemented function)
37 my $opt_fussy = 1;                  # Non-zero = Create only if we have a RETURNS section
38 my $opt_verbose = 0;                # >0 = verbosity. Can be given multiple times (for debugging)
39 my @opt_header_file_list = ();
40 my @opt_spec_file_list = ();
41 my @opt_source_file_list = ();
42
43 # All the collected details about all the .spec files being processed
44 my %spec_files;
45 # All the collected details about all the source files being processed
46 my %source_files;
47
48 # useful globals
49 my $pwd = `pwd`."/";
50 $pwd =~ s/\n//;
51 my @datetime = localtime;
52 my @months = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
53             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
54 my $year = $datetime[5] + 1900;
55 my $date = "$months[$datetime[4]] $year";
56
57 sub usage
58 {
59   print "\nCreate API Documentation from Wine source code.\n\n",
60         "Usage: c2man.pl [options] {-w <spec>} {-I <include>} {<source>}\n",
61         "Where: <spec> is a .spec file giving a DLL's exports.\n",
62         "       <include> is an include directory used by the DLL.\n",
63         "       <source> is a source file of the DLL.\n",
64         "       The above can be given multiple times on the command line, as appropriate.\n",
65         "Options:\n",
66         " -Th      : Output HTML instead of a man page\n",
67         " -Ts      : Output SGML (Docbook source) instead of a man page\n",
68         " -o <dir> : Create output in <dir>, default is \"",$opt_output_directory,"\"\n",
69         " -s <sect>: Set manual section to <sect>, default is ",$opt_manual_section,"\n",
70         " -e       : Output \"FIXME\" documentation from empty comments.\n",
71         " -v       : Verbosity. Can be given more than once for more detail.\n";
72 }
73
74 # Print usage if we're called with no args
75 if( @ARGV == 0)
76 {
77   usage();
78 }
79
80 # Process command line options
81 while(defined($_ = shift @ARGV))
82 {
83   if( s/^-// )
84   {
85     # An option.
86     for ($_)
87     {
88       /^o$/  && do { $opt_output_directory = shift @ARGV; last; };
89       s/^S// && do { $opt_manual_section   = $_;          last; };
90       /^Th$/ && do { $opt_output_format  = "h";           last; };
91       /^Ts$/ && do { $opt_output_format  = "s";           last; };
92       /^v$/  && do { $opt_verbose++;                      last; };
93       /^e$/  && do { $opt_output_empty = 1;               last; };
94       /^L$/  && do { last; };
95       /^w$/  && do { @opt_spec_file_list = (@opt_spec_file_list, shift @ARGV); last; };
96       s/^I// && do { my $include = $_."/*.h";
97                      $include =~ s/\/\//\//g;
98                      my $have_headers = `ls $include >/dev/null 2>&1`;
99                      if ($? >> 8 == 0) { @opt_header_file_list = (@opt_header_file_list, $include); }
100                      last;
101                    };
102       s/^R// && do { if ($_ =~ /^\//) { $opt_wine_root_dir = $_; }
103                      else { $opt_wine_root_dir = `cd $pwd/$_ && pwd`; }
104                      $opt_wine_root_dir =~ s/\n//;
105                      $opt_wine_root_dir =~ s/\/\//\//g;
106                      if (! $opt_wine_root_dir =~ /\/$/ ) { $opt_wine_root_dir = $opt_wine_root_dir."/"; };
107                      last;
108              };
109       die "Unrecognised option $_\n";
110     }
111   }
112   else
113   {
114     # A source file.
115     push (@opt_source_file_list, $_);
116   }
117 }
118
119 # Remove duplicate include directories
120 my %htmp;
121 @opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list);
122
123 if ($opt_verbose > 3)
124 {
125   print "Output dir:'".$opt_output_directory."'\n";
126   print "Section   :'".$opt_manual_section."'\n";
127   print "Format  :'".$opt_output_format."'\n";
128   print "Root    :'".$opt_wine_root_dir."'\n";
129   print "Spec files:'@opt_spec_file_list'\n";
130   print "Includes  :'@opt_header_file_list'\n";
131   print "Sources   :'@opt_source_file_list'\n";
132 }
133
134 if (@opt_spec_file_list == 0)
135 {
136   exit 0; # Don't bother processing non-dll files
137 }
138
139 # Read in each .spec files exports and other details
140 while(my $spec_file = shift @opt_spec_file_list)
141 {
142   process_spec_file($spec_file);
143 }
144
145 if ($opt_verbose > 3)
146 {
147     foreach my $spec_file ( keys %spec_files )
148     {
149         print "in '$spec_file':\n";
150         my $spec_details = $spec_files{$spec_file}[0];
151         my $exports = $spec_details->{EXPORTS};
152         for (@$exports)
153         {
154            print @$_[0].",".@$_[1].",".@$_[2].",".@$_[3]."\n";
155         }
156     }
157 }
158
159 # Extract and output the comments from each source file
160 while(defined($_ = shift @opt_source_file_list))
161 {
162   process_source_file($_);
163 }
164
165 # Write the index files for each spec
166 process_index_files();
167
168 # Write the master index file
169 output_master_index_files();
170
171 exit 0;
172
173
174 # Generate the list of exported entries for the dll
175 sub process_spec_file
176 {
177   my $spec_name = shift(@_);
178   my $dll_name  = $spec_name;
179   $dll_name =~ s/\..*//;       # Strip the file extension
180   my $uc_dll_name  = uc $dll_name;
181
182   my $spec_details =
183   {
184     NAME => $spec_name,
185     DLL_NAME => $dll_name,
186     NUM_EXPORTS => 0,
187     NUM_STUBS => 0,
188     NUM_FUNCS => 0,
189     NUM_FORWARDS => 0,
190     NUM_VARS => 0,
191     NUM_DOCS => 0,
192     CONTRIBUTORS => [ ],
193     SOURCES => [ ],
194     DESCRIPTION => [ ],
195     EXPORTS => [ ],
196     EXPORTED_NAMES => { },
197     IMPLEMENTATION_NAMES => { },
198     EXTRA_COMMENTS => [ ],
199     CURRENT_EXTRA => [ ] ,
200   };
201
202   if ($opt_verbose > 0)
203   {
204     print "Processing ".$spec_name."\n";
205   }
206
207   # We allow opening to fail just to cater for the peculiarities of
208   # the Wine build system. This doesn't hurt, in any case
209   open(SPEC_FILE, "<$spec_name") || return;
210
211   while(<SPEC_FILE>)
212   {
213     s/^\s+//;            # Strip leading space
214     s/\s+\n$/\n/;        # Strip trailing space
215     s/\s+/ /g;           # Strip multiple tabs & spaces to a single space
216     s/\s*#.*//;          # Strip comments
217     s/\(.*\)/ /;         # Strip arguments
218     s/ \-[a-z0-9]+//g;   # Strip modifiers
219     s/\s+/ /g;           # Strip multiple tabs & spaces to a single space (again)
220     s/\n$//;             # Strip newline
221
222     if( /^(([0-9]+)|@) / )
223     {
224       # This line contains an exported symbol
225       my ($ordinal, $call_convention, $exported_name, $implementation_name) = split(' ');
226
227       for ($call_convention)
228       {
229         /^(cdecl|stdcall|varargs|pascal|pascal16)$/
230                  && do { $spec_details->{NUM_FUNCS}++;    last; };
231         /^(variable|equate)$/
232                  && do { $spec_details->{NUM_VARS}++;     last; };
233         /^(forward|extern)$/
234                  && do { $spec_details->{NUM_FORWARDS}++; last; };
235         /^stub$/ && do { $spec_details->{NUM_STUBS}++;    last; };
236         if ($opt_verbose > 0)
237         {
238           print "Warning: didn't recognise convention \'",$call_convention,"'\n";
239         }
240         last;
241       }
242
243       # Convert ordinal only names so we can find them later
244       if ($exported_name eq "@")
245       {
246         $exported_name = $uc_dll_name.'_'.$ordinal;
247       }
248       if (!defined($implementation_name))
249       {
250         $implementation_name = $exported_name;
251       }
252       # Add indices for the exported and implementation names
253       $spec_details->{EXPORTED_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
254       if ($implementation_name ne $exported_name)
255       {
256         $spec_details->{IMPLEMENTATION_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
257       }
258
259       # Add the exported entry
260       $spec_details->{NUM_EXPORTS}++;
261       my $documented = 0;
262       my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
263       push (@{$spec_details->{EXPORTS}},[@export]);
264     }
265   }
266   close(SPEC_FILE);
267
268   # Add this .spec files details to the list of .spec files
269   $spec_files{$uc_dll_name} = [$spec_details];
270 }
271
272 # Read each source file, extract comments, and generate API documentation if appropriate
273 sub process_source_file
274 {
275   my $source_file = shift(@_);
276   my $source_details =
277   {
278     CONTRIBUTORS => [ ],
279     DEBUG_CHANNEL => "",
280   };
281   my $comment =
282   {
283     FILE => $source_file,
284     COMMENT_NAME => "",
285     ALT_NAME => "",
286     DLL_NAME => "",
287     ORDINAL => "",
288     RETURNS => "",
289     PROTOTYPE => [],
290     TEXT => [],
291   };
292   my $parse_state = 0;
293   my $ignore_blank_lines = 1;
294   my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export
295
296   if ($opt_verbose > 0)
297   {
298     print "Processing ".$source_file."\n";
299   }
300   open(SOURCE_FILE,"<$source_file") || die "couldn't open ".$source_file."\n";
301
302   # Add this source file to the list of source files
303   $source_files{$source_file} = [$source_details];
304
305   while(<SOURCE_FILE>)
306   {
307     s/\n$//;   # Strip newline
308     s/^\s+//;  # Strip leading space
309     s/\s+$//;  # Strip trailing space
310     if (! /^\*\|/ )
311     {
312       # Strip multiple tabs & spaces to a single space
313       s/\s+/ /g;
314     }
315
316     if ( / +Copyright *(\([Cc]\))*[0-9 \-\,\/]*([[:alpha:][:^ascii:] \.\-]+)/ )
317     {
318       # Extract a contributor to this file
319       my $contributor = $2;
320       $contributor =~ s/ *$//;
321       $contributor =~ s/^by //;
322       $contributor =~ s/\.$//;
323       $contributor =~ s/ (for .*)/ \($1\)/;
324       if ($contributor ne "")
325       {
326         if ($opt_verbose > 3)
327         {
328           print "Info: Found contributor:'".$contributor."'\n";
329         }
330         push (@{$source_details->{CONTRIBUTORS}},$contributor);
331       }
332     }
333     elsif ( /WINE_DEFAULT_DEBUG_CHANNEL\(([A-Za-z]*)\)/ )
334     {
335       # Extract the debug channel to use
336       if ($opt_verbose > 3)
337       {
338         print "Info: Found debug channel:'".$1."'\n";
339       }
340       $source_details->{DEBUG_CHANNEL} = $1;
341     }
342
343     if ($parse_state == 0) # Searching for a comment
344     {
345       if ( /^\/\**$/ )
346       {
347         # Found a comment start
348         $comment->{COMMENT_NAME} = "";
349         $comment->{ALT_NAME} = "";
350         $comment->{DLL_NAME} = "";
351         $comment->{ORDINAL} = "";
352         $comment->{RETURNS} = "";
353         $comment->{PROTOTYPE} = [];
354         $comment->{TEXT} = [];
355         $ignore_blank_lines = 1;
356         $extra_comment = 0;
357         $parse_state = 3;
358       }
359     }
360     elsif ($parse_state == 1) # Reading in a comment
361     {
362       if ( /^\**\// )
363       {
364         # Found the end of the comment
365         $parse_state = 2;
366       }
367       elsif ( s/^\*\|/\|/ )
368       {
369         # A line of comment not meant to be pre-processed
370         push (@{$comment->{TEXT}},$_); # Add the comment text
371       }
372       elsif ( s/^ *\** *// )
373       {
374         # A line of comment, starting with an asterisk
375         if ( /^[A-Z]+$/ || $_ eq "")
376         {
377           # This is a section start, so skip blank lines before and after it.
378           my $last_line = pop(@{$comment->{TEXT}});
379           if (defined($last_line) && $last_line ne "")
380           {
381             # Put it back
382             push (@{$comment->{TEXT}},$last_line);
383           }
384           if ( /^[A-Z]+$/ )
385           {
386             $ignore_blank_lines = 1;
387           }
388           else
389           {
390             $ignore_blank_lines = 0;
391           }
392         }
393
394         if ($ignore_blank_lines == 0 || $_ ne "")
395         {
396           push (@{$comment->{TEXT}},$_); # Add the comment text
397         }
398       }
399       else
400       {
401         # This isn't a well formatted comment: look for the next one
402         $parse_state = 0;
403       }
404     }
405     elsif ($parse_state == 2) # Finished reading in a comment
406     {
407       if ( /(WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ )
408       {
409         # Comment is followed by a function definition
410         $parse_state = 4; # Fall through to read prototype
411       }
412       else
413       {
414         # Allow cpp directives and blank lines between the comment and prototype
415         if ($extra_comment == 1)
416         {
417           # An extra comment not followed by a function definition
418           $parse_state = 5; # Fall through to process comment
419         }
420         elsif (!/^\#/ && !/^ *$/ && !/^__ASM_GLOBAL_FUNC/)
421         {
422           # This isn't a well formatted comment: look for the next one
423           if ($opt_verbose > 1)
424           {
425             print "Info: Function '",$comment->{COMMENT_NAME},"' not followed by prototype.\n";
426           }
427           $parse_state = 0;
428         }
429       }
430     }
431     elsif ($parse_state == 3) # Reading in the first line of a comment
432     {
433       s/^ *\** *//;
434       if ( /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
435       {
436         # Found a correctly formed "ApiName (DLLNAME.Ordinal)" line.
437         $comment->{COMMENT_NAME} = $1;
438         $comment->{DLL_NAME} = uc $3;
439         $comment->{ORDINAL} = $4;
440         $comment->{DLL_NAME} =~ s/^KERNEL$/KRNL386/; # Too many of these to ignore, _old_ code
441         $parse_state = 1;
442       }
443       elsif ( /^([A-Za-z0-9_]+) +\{([A-Za-z0-9_]+)\}$/ )
444       {
445         # Found a correctly formed "CommentTitle {DLLNAME}" line (extra documentation)
446         $comment->{COMMENT_NAME} = $1;
447         $comment->{DLL_NAME} = uc $2;
448         $comment->{ORDINAL} = "";
449         $extra_comment = 1;
450         $parse_state = 1;
451       }
452       else
453       {
454         # This isn't a well formatted comment: look for the next one
455         $parse_state = 0;
456       }
457     }
458
459     if ($parse_state == 4) # Reading in the function definition
460     {
461       push (@{$comment->{PROTOTYPE}},$_);
462       # Strip comments from the line before checking for ')'
463       my $stripped_line = $_;
464       $stripped_line =~ s/ *(\/\* *)(.*?)( *\*\/ *)//;
465       if ( $stripped_line =~ /\)/ )
466       {
467         # Strip a blank last line
468         my $last_line = pop(@{$comment->{TEXT}});
469         if (defined($last_line) && $last_line ne "")
470         {
471           # Put it back
472           push (@{$comment->{TEXT}},$last_line);
473         }
474
475         if ($opt_output_empty != 0 && @{$comment->{TEXT}} == 0)
476         {
477           # Create a 'not implemented' comment
478           @{$comment->{TEXT}} = ("fixme: This function has not yet been documented.");
479         }
480         $parse_state = 5;
481       }
482     }
483
484     if ($parse_state == 5) # Processing the comment
485     {
486       # Process it, if it has any text
487       if (@{$comment->{TEXT}} > 0)
488       {
489         if ($extra_comment == 1)
490         {
491           process_extra_comment($comment);
492         }
493         else
494         {
495           @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
496           process_comment($comment);
497         }
498       }
499       elsif ($opt_verbose > 1 && $opt_output_empty == 0)
500       {
501         print "Info: Function '",$comment->{COMMENT_NAME},"' has no documentation.\n";
502       }
503       $parse_state = 0;
504     }
505   }
506   close(SOURCE_FILE);
507 }
508
509 # Standardise a comments text for consistency
510 sub process_comment_text
511 {
512   my $comment = shift(@_);
513
514   for (@{$comment->{TEXT}})
515   {
516     if (! /^\|/ )
517     {
518       # Map I/O values. These come in too many formats to standardise now....
519       s/\[I\]|\[i\]|\[in\]|\[IN\]/\[In\] /g;
520       s/\[O\]|\[o\]|\[out\]\[OUT\]/\[Out\]/g;
521       s/\[I\/O\]|\[I\,O\]|\[i\/o\]|\[in\/out\]|\[IN\/OUT\]/\[In\/Out\]/g;
522       # TRUE/FALSE/NULL are defines, capitilise them
523       s/True|true/TRUE/g;
524       s/False|false/FALSE/g;
525       s/Null|null/NULL/g;
526       # Preferred capitalisations
527       s/ wine| WINE/ Wine/g;
528       s/ API | api / Api /g;
529       s/DLL| Dll /dll /g;
530       s/ URL | url / Url /g;
531       s/WIN16|win16/Win16/g;
532       s/WIN32|win32/Win32/g;
533       s/WIN64|win64/Win64/g;
534       s/ ID | id / Id /g;
535       # Grammar
536       s/([a-z])\.([A-Z])/$1\. $2/g; # Space after full stop
537       s/ \:/\:/g;                   # Colons to the left
538       s/ \;/\;/g;                   # Semi-colons too
539       # Common idioms
540       s/^See ([A-Za-z0-9_]+)\.$/See $1\(\)\./;                # Referring to A version from W
541       s/^Unicode version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Ditto
542       s/^PARAMETERS$/PARAMS/;  # Name of parameter section should be 'PARAMS'
543       # Trademarks
544       s/( |\.)(MS|Microsoft|microsoft)( |\.)/$1Microsoft\(tm\)$3/g;
545       s/( |\.)(Windows|windows|windoze|winblows)( |\.)/$1Windows\(tm\)$3/g;
546       s/( |\.)(DOS|dos|msdos)( |\.)/$1MS-DOS\(tm\)$3/g;
547       s/( |\.)(UNIX|Unix|unix)( |\.)/$1Unix\(tm\)$3/g;
548       # Abbreviations
549       s/( chars)/characters/g;
550       s/( info )/ information /g;
551       s/( app )/ application /g;
552       s/( apps )/ applications /g;
553     }
554   }
555 }
556
557 # Standardise our comment and output it if it is suitable.
558 sub process_comment
559 {
560   my $comment = shift(@_);
561
562   # Don't process this comment if the function isnt exported
563   my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
564
565   if (!defined($spec_details))
566   {
567     if ($opt_verbose > 2)
568     {
569       print "Warning: Function '".$comment->{COMMENT_NAME}."' belongs to '".
570             $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
571     }
572     return;
573   }
574
575   if ($comment->{COMMENT_NAME} eq "@")
576   {
577     # Create an implementation name
578     $comment->{COMMENT_NAME} = $comment->{DLL_NAME}."_".$comment->{ORDINAL};
579   }
580
581   my $exported_names = $spec_details->{EXPORTED_NAMES};
582   my $export_index = $exported_names->{$comment->{COMMENT_NAME}};
583
584   if (!defined($export_index))
585   {
586     # Perhaps the comment uses the implementation name?
587     my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES};
588     $export_index = $implementation_names->{$comment->{COMMENT_NAME}};
589   }
590   if (!defined($export_index))
591   {
592     # This function doesn't appear to be exported. hmm.
593     if ($opt_verbose > 2)
594     {
595       print "Warning: Function '".$comment->{COMMENT_NAME}."' claims to belong to '".
596             $comment->{DLL_NAME}."' but is not exported by it: not processing it.\n";
597     }
598     return;
599   }
600
601   # When the function is exported twice we have the second name below the first
602   # (you see this a lot in ntdll, but also in some other places).
603   my $first_line = ${@{$comment->{TEXT}}}[1];
604
605   if ( $first_line =~ /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
606   {
607     # Found a second name - mark it as documented
608     my $alt_index = $exported_names->{$1};
609     if (defined($alt_index))
610     {
611       if ($opt_verbose > 2)
612       {
613         print "Info: Found alternate name '",$1,"\n";
614       }
615       my $alt_export = @{$spec_details->{EXPORTS}}[$alt_index];
616       @$alt_export[4] |= 1;
617       $spec_details->{NUM_DOCS}++;
618       ${@{$comment->{TEXT}}}[1] = "";
619     }
620   }
621
622   if (@{$spec_details->{CURRENT_EXTRA}})
623   {
624     # We have an extra comment that might be related to this one
625     my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
626     my $current_name = $current_comment->{COMMENT_NAME};
627     if ($comment->{COMMENT_NAME} =~ /^$current_name/ && $comment->{COMMENT_NAME} ne $current_name)
628     {
629       if ($opt_verbose > 2)
630       {
631         print "Linking ",$comment->{COMMENT_NAME}," to $current_name\n";
632       }
633       # Add a reference to this comment to our extra comment
634       push (@{$current_comment->{TEXT}}, $comment->{COMMENT_NAME}."()","");
635     }
636   }
637
638   # We want our docs generated using the implementation name, so they are unique
639   my $export = @{$spec_details->{EXPORTS}}[$export_index];
640   $comment->{COMMENT_NAME} = @$export[3];
641   $comment->{ALT_NAME} = @$export[2];
642
643   # Mark the function as documented
644   $spec_details->{NUM_DOCS}++;
645   @$export[4] |= 1;
646
647   # This file is used by the DLL - Make sure we get our contributors right
648   push (@{$spec_details->{SOURCES}},$comment->{FILE});
649
650   # If we have parameter comments in the prototype, extract them
651   my @parameter_comments;
652   for (@{$comment->{PROTOTYPE}})
653   {
654     s/ *\, */\,/g; # Strip spaces from around commas
655
656     if ( s/ *(\/\* *)(.*?)( *\*\/ *)// ) # Strip out comment
657     {
658       my $parameter_comment = $2;
659       if (!$parameter_comment =~ /^\[/ )
660       {
661         # Add [IO] markers so we format the comment correctly
662         $parameter_comment = "[fixme] ".$parameter_comment;
663       }
664       if ( /( |\*)([A-Za-z_]{1}[A-Za-z_0-9]*)(\,|\))/ )
665       {
666         # Add the parameter name
667         $parameter_comment = $2." ".$parameter_comment;
668       }
669       push (@parameter_comments, $parameter_comment);
670     }
671   }
672
673   # If we extracted any prototype comments, add them to the comment text.
674   if (@parameter_comments)
675   {
676     @parameter_comments = ("PARAMS", @parameter_comments);
677     my @new_comment = ();
678     my $inserted_params = 0;
679
680     for (@{$comment->{TEXT}})
681     {
682       if ( $inserted_params == 0 && /^[A-Z]+$/ )
683       {
684         # Found a section header, so this is where we insert
685         push (@new_comment, @parameter_comments);
686         $inserted_params = 1;
687       }
688       push (@new_comment, $_);
689     }
690     if ($inserted_params == 0)
691     {
692       # Add them to the end
693       push (@new_comment, @parameter_comments);
694     }
695     $comment->{TEXT} = [@new_comment];
696   }
697
698   if ($opt_fussy == 1 && $opt_output_empty == 0)
699   {
700     # Reject any comment that doesn't have a description or a RETURNS section.
701     # This is the default for now, 'coz many comments aren't suitable.
702     my $found_returns = 0;
703     my $found_description_text = 0;
704     my $in_description = 0;
705     for (@{$comment->{TEXT}})
706     {
707       if ( /^RETURNS$/ )
708       {
709         $found_returns = 1;
710         $in_description = 0;
711       }
712       elsif ( /^DESCRIPTION$/ )
713       {
714         $in_description = 1;
715       }
716       elsif ($in_description == 1)
717       {
718         if ( !/^[A-Z]+$/ )
719         {
720           if ( /^See ([A-Za-z0-9_]+)\.$/ || /^Unicode version of ([A-Za-z0-9_]+)\.$/)
721           {
722             # Dont reject comments that refer to their A/W couterpart
723             $found_returns = 1;
724           }
725           $found_description_text = 1;
726         }
727         else
728         {
729           $in_description = 0;
730         }
731       }
732     }
733     if ($found_returns == 0 || $found_description_text == 0)
734     {
735       if ($opt_verbose > 2)
736       {
737         print "Info: Function '",$comment->{COMMENT_NAME},"' has no ",
738               "description and/or RETURNS section, skipping\n";
739       }
740       $spec_details->{NUM_DOCS}--;
741       @$export[4] &= ~1;
742       return;
743     }
744   }
745
746   process_comment_text($comment);
747
748   # Strip the prototypes return value, call convention, name and brackets
749   # (This leaves it as a list of types and names, or empty for void functions)
750   my $prototype = join(" ", @{$comment->{PROTOTYPE}});
751   $prototype =~ s/  / /g;
752   $prototype =~ s/^(.*?) (WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16) (.*?)\( *(.*)/$4/;
753   $comment->{RETURNS} = $1;
754   $prototype =~ s/ *\).*//;        # Strip end bracket
755   $prototype =~ s/ *\* */\*/g;     # Strip space around pointers
756   $prototype =~ s/ *\, */\,/g;     # Strip space around commas
757   $prototype =~ s/^(void|VOID)$//; # If void, leave blank
758   $prototype =~ s/\*([A-Za-z_])/\* $1/g; # Seperate pointers from parameter name
759   @{$comment->{PROTOTYPE}} = split ( /,/ ,$prototype);
760
761   # FIXME: If we have no parameters, make sure we have a PARAMS: None. section
762
763   # Find header file
764   # FIXME: This sometimes gives the error "sh: <file>.h: Permission denied" - why?
765   my $h_file = "";
766   my $tmp = "grep -s -l $comment->{COMMENT_NAME} @opt_header_file_list 2>/dev/null";
767   $tmp = `$tmp`;
768   my $exit_value  = $? >> 8;
769   if ($exit_value == 0)
770   {
771     $tmp =~ s/\n.*//;
772     if ($tmp ne "")
773     {
774       $h_file = `basename $tmp`;
775     }
776   }
777   else
778   {
779     $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null";
780     $tmp = `$tmp`;
781     $exit_value  = $? >> 8;
782     if ($exit_value == 0)
783     {
784       $tmp =~ s/\n.*//;
785       if ($tmp ne "")
786       {
787         $h_file = `basename $tmp`;
788       }
789     }
790   }
791   $h_file =~ s/^ *//;
792   $h_file =~ s/\n//;
793   if ($h_file eq "")
794   {
795     $h_file = "Not defined in a Wine header";
796   }
797   else
798   {
799     $h_file = "Defined in \"".$h_file."\"";
800   }
801
802   # Find source file
803   my $c_file = $comment->{FILE};
804   if ($opt_wine_root_dir ne "")
805   {
806     my $cfile = $pwd."/".$c_file;     # Current dir + file
807     $cfile =~ s/(.+)(\/.*$)/$1/;      # Strip the filename
808     $cfile = `cd $cfile && pwd`;      # Strip any relative parts (e.g. "../../")
809     $cfile =~ s/\n//;                 # Strip newline
810     my $newfile = $c_file;
811     $newfile =~ s/(.+)(\/.*$)/$2/;    # Strip all but the filename
812     $cfile = $cfile."/".$newfile;     # Append filename to base path
813     $cfile =~ s/$opt_wine_root_dir//; # Get rid of the root directory
814     $cfile =~ s/\/\//\//g;            # Remove any double slashes
815     $cfile =~ s/^\/+//;               # Strip initial directory slash
816     $c_file = $cfile;
817   }
818   $c_file = "Implemented in \"".$c_file."\"";
819
820   # Add the implementation details
821   push (@{$comment->{TEXT}}, "IMPLEMENTATION","",$h_file,"",$c_file);
822
823   my $source_details = $source_files{$comment->{FILE}}[0];
824   if ($source_details->{DEBUG_CHANNEL} ne "")
825   {
826     push (@{$comment->{TEXT}}, "", "Debug channel \"".$source_details->{DEBUG_CHANNEL}."\"");
827   }
828
829   # Write out the documentation for the API
830   output_comment($comment)
831 }
832
833 # process our extra comment and output it if it is suitable.
834 sub process_extra_comment
835 {
836   my $comment = shift(@_);
837
838   my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
839
840   if (!defined($spec_details))
841   {
842     if ($opt_verbose > 2)
843     {
844       print "Warning: Extra comment '".$comment->{COMMENT_NAME}."' belongs to '".
845             $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
846     }
847     return;
848   }
849
850   # Check first to see if this is documentation for the DLL.
851   if ($comment->{COMMENT_NAME} eq $comment->{DLL_NAME})
852   {
853     if ($opt_verbose > 2)
854     {
855       print "Info: Found DLL documentation\n";
856     }
857     for (@{$comment->{TEXT}})
858     {
859       push (@{$spec_details->{DESCRIPTION}}, $_);
860     }
861     return;
862   }
863
864   # Add the comment to the DLL page as a link
865   push (@{$spec_details->{EXTRA_COMMENTS}},$comment->{COMMENT_NAME});
866
867   # If we have a prototype, process as a regular comment
868   if (@{$comment->{PROTOTYPE}})
869   {
870     $comment->{ORDINAL} = "@";
871
872     # Add an index for the comment name
873     $spec_details->{EXPORTED_NAMES}{$comment->{COMMENT_NAME}} = $spec_details->{NUM_EXPORTS};
874
875     # Add a fake exported entry
876     $spec_details->{NUM_EXPORTS}++;
877     my ($ordinal, $call_convention, $exported_name, $implementation_name, $documented) =
878      ("@", "fake", $comment->{COMMENT_NAME}, $comment->{COMMENT_NAME}, 0);
879     my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
880     push (@{$spec_details->{EXPORTS}},[@export]);
881     @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
882     process_comment($comment);
883     return;
884   }
885
886   if ($opt_verbose > 0)
887   {
888     print "Processing ",$comment->{COMMENT_NAME},"\n";
889   }
890
891   if (@{$spec_details->{CURRENT_EXTRA}})
892   {
893     my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
894
895     if ($opt_verbose > 0)
896     {
897       print "Processing old current: ",$current_comment->{COMMENT_NAME},"\n";
898     }
899     # Output the current comment
900     process_comment_text($current_comment);
901     output_open_api_file($current_comment->{COMMENT_NAME});
902     output_api_header($current_comment);
903     output_api_name($current_comment);
904     output_api_comment($current_comment);
905     output_api_footer($current_comment);
906     output_close_api_file();
907   }
908
909   if ($opt_verbose > 2)
910   {
911     print "Setting current to ",$comment->{COMMENT_NAME},"\n";
912   }
913
914   my $comment_copy =
915   {
916     FILE => $comment->{FILE},
917     COMMENT_NAME => $comment->{COMMENT_NAME},
918     ALT_NAME => $comment->{ALT_NAME},
919     DLL_NAME => $comment->{DLL_NAME},
920     ORDINAL => $comment->{ORDINAL},
921     RETURNS => $comment->{RETURNS},
922     PROTOTYPE => [],
923     TEXT => [],
924   };
925
926   for (@{$comment->{TEXT}})
927   {
928     push (@{$comment_copy->{TEXT}}, $_);
929   }
930   # Set this comment to be the current extra comment
931   @{$spec_details->{CURRENT_EXTRA}} = ($comment_copy);
932 }
933
934 # Write a standardised comment out in the appropriate format
935 sub output_comment
936 {
937   my $comment = shift(@_);
938
939   if ($opt_verbose > 0)
940   {
941     print "Processing ",$comment->{COMMENT_NAME},"\n";
942   }
943
944   if ($opt_verbose > 4)
945   {
946     print "--PROTO--\n";
947     for (@{$comment->{PROTOTYPE}})
948     {
949       print "'".$_."'\n";
950     }
951
952     print "--COMMENT--\n";
953     for (@{$comment->{TEXT} })
954     {
955       print $_."\n";
956     }
957   }
958
959   output_open_api_file($comment->{COMMENT_NAME});
960   output_api_header($comment);
961   output_api_name($comment);
962   output_api_synopsis($comment);
963   output_api_comment($comment);
964   output_api_footer($comment);
965   output_close_api_file();
966 }
967
968 # Write out an index file for each .spec processed
969 sub process_index_files
970 {
971   foreach my $spec_file (keys %spec_files)
972   {
973     my $spec_details = $spec_files{$spec_file}[0];
974     if (defined ($spec_details->{DLL_NAME}))
975     {
976       if (@{$spec_details->{CURRENT_EXTRA}})
977       {
978         # We have an unwritten extra comment, write it
979         my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
980         process_extra_comment($current_comment);
981         @{$spec_details->{CURRENT_EXTRA}} = ();
982        }
983        output_spec($spec_details);
984     }
985   }
986 }
987
988 # Write a spec files documentation out in the appropriate format
989 sub output_spec
990 {
991   my $spec_details = shift(@_);
992
993   if ($opt_verbose > 2)
994   {
995     print "Writing:",$spec_details->{DLL_NAME},"\n";
996   }
997
998   # Use the comment output functions for consistency
999   my $comment =
1000   {
1001     FILE => $spec_details->{DLL_NAME},
1002     COMMENT_NAME => $spec_details->{DLL_NAME}.".dll",
1003     ALT_NAME => $spec_details->{DLL_NAME},
1004     DLL_NAME => "",
1005     ORDINAL => "",
1006     RETURNS => "",
1007     PROTOTYPE => [],
1008     TEXT => [],
1009   };
1010   my $total_implemented = $spec_details->{NUM_FORWARDS} + $spec_details->{NUM_VARS} +
1011      $spec_details->{NUM_FUNCS};
1012   my $percent_implemented = 0;
1013   if ($total_implemented)
1014   {
1015     $percent_implemented = $total_implemented /
1016      ($total_implemented + $spec_details->{NUM_STUBS}) * 100;
1017   }
1018   $percent_implemented = int($percent_implemented);
1019   my $percent_documented = 0;
1020   if ($spec_details->{NUM_DOCS})
1021   {
1022     # Treat forwards and data as documented funcs for statistics
1023     $percent_documented = $spec_details->{NUM_DOCS} / $spec_details->{NUM_FUNCS} * 100;
1024     $percent_documented = int($percent_documented);
1025   }
1026
1027   # Make a list of the contributors to this DLL. Do this only for the source
1028   # files that make up the DLL, because some directories specify multiple dlls.
1029   my @contributors;
1030
1031   for (@{$spec_details->{SOURCES}})
1032   {
1033     my $source_details = $source_files{$_}[0];
1034     for (@{$source_details->{CONTRIBUTORS}})
1035     {
1036       push (@contributors, $_);
1037     }
1038   }
1039
1040   my %saw;
1041   @contributors = grep(!$saw{$_}++, @contributors); # remove dups, from perlfaq4 manpage
1042   @contributors = sort @contributors;
1043
1044   # Remove duplicates and blanks
1045   for(my $i=0; $i<@contributors; $i++)
1046   {
1047     if ($i > 0 && ($contributors[$i] =~ /$contributors[$i-1]/ || $contributors[$i-1] eq ""))
1048     {
1049       $contributors[$i-1] = $contributors[$i];
1050     }
1051   }
1052   undef %saw;
1053   @contributors = grep(!$saw{$_}++, @contributors);
1054
1055   if ($opt_verbose > 3)
1056   {
1057     print "Contributors:\n";
1058     for (@contributors)
1059     {
1060       print "'".$_."'\n";
1061     }
1062   }
1063   my $contribstring = join (", ", @contributors);
1064
1065   # Create the initial comment text
1066   @{$comment->{TEXT}} = (
1067     "NAME",
1068     $comment->{COMMENT_NAME}
1069   );
1070
1071   # Add the description, if we have one
1072   if (@{$spec_details->{DESCRIPTION}})
1073   {
1074     push (@{$comment->{TEXT}}, "DESCRIPTION");
1075     for (@{$spec_details->{DESCRIPTION}})
1076     {
1077       push (@{$comment->{TEXT}}, $_);
1078     }
1079   }
1080
1081   # Add the statistics and contributors
1082   push (@{$comment->{TEXT}},
1083     "STATISTICS",
1084     "Forwards: ".$spec_details->{NUM_FORWARDS},
1085     "Variables: ".$spec_details->{NUM_VARS},
1086     "Stubs: ".$spec_details->{NUM_STUBS},
1087     "Functions: ".$spec_details->{NUM_FUNCS},
1088     "Exports-Total: ".$spec_details->{NUM_EXPORTS},
1089     "Implemented-Total: ".$total_implemented." (".$percent_implemented."%)",
1090     "Documented-Total: ".$spec_details->{NUM_DOCS}." (".$percent_documented."%)",
1091     "CONTRIBUTORS",
1092     "The following people hold copyrights on the source files comprising this dll:",
1093     "",
1094     $contribstring,
1095     "Note: This list may not be complete.",
1096     "For a complete listing, see the Files \"AUTHORS\" and \"Changelog\" in the Wine source tree.",
1097     "",
1098   );
1099
1100   if ($opt_output_format eq "h")
1101   {
1102     # Add the exports to the comment text
1103     push (@{$comment->{TEXT}},"EXPORTS");
1104     my $exports = $spec_details->{EXPORTS};
1105     for (@$exports)
1106     {
1107       my $line = "";
1108
1109       # @$_ => ordinal, call convention, exported name, implementation name, documented;
1110       if (@$_[1] eq "forward")
1111       {
1112         my $forward_dll = @$_[3];
1113         $forward_dll =~ s/\.(.*)//;
1114         $line = @$_[2]." (forward to ".$1."() in ".$forward_dll."())";
1115       }
1116       elsif (@$_[1] eq "extern")
1117       {
1118         $line = @$_[2]." (extern)";
1119       }
1120       elsif (@$_[1] eq "stub")
1121       {
1122         $line = @$_[2]." (stub)";
1123       }
1124       elsif (@$_[1] eq "fake")
1125       {
1126         # Don't add this function here, it gets listed with the extra documentation
1127       }
1128       elsif (@$_[1] eq "equate" || @$_[1] eq "variable")
1129       {
1130         $line = @$_[2]." (data)";
1131       }
1132       else
1133       {
1134         # A function
1135         if (@$_[4] & 1)
1136         {
1137           # Documented
1138           $line = @$_[2]." (implemented as ".@$_[3]."())";
1139           if (@$_[2] ne @$_[3])
1140           {
1141             $line = @$_[2]." (implemented as ".@$_[3]."())";
1142           }
1143           else
1144           {
1145             $line = @$_[2]."()";
1146           }
1147         }
1148         else
1149         {
1150           $line = @$_[2]." (not documented)";
1151         }
1152       }
1153       if ($line ne "")
1154       {
1155         push (@{$comment->{TEXT}}, $line, "");
1156       }
1157     }
1158
1159     # Add links to the extra documentation
1160     if (@{$spec_details->{EXTRA_COMMENTS}})
1161     {
1162       push (@{$comment->{TEXT}}, "SEE ALSO");
1163       my %htmp;
1164       @{$spec_details->{EXTRA_COMMENTS}} = grep(!$htmp{$_}++, @{$spec_details->{EXTRA_COMMENTS}});
1165       for (@{$spec_details->{EXTRA_COMMENTS}})
1166       {
1167         push (@{$comment->{TEXT}}, $_."()", "");
1168       }
1169     }
1170   }
1171   # Write out the document
1172   output_open_api_file($spec_details->{DLL_NAME});
1173   output_api_header($comment);
1174   output_api_comment($comment);
1175   output_api_footer($comment);
1176   output_close_api_file();
1177
1178   # Add this dll to the database of dll names
1179   my $output_file = $opt_output_directory."/dlls.db";
1180
1181   # Append the dllname to the output db of names
1182   open(DLLDB,">>$output_file") || die "Couldn't create $output_file\n";
1183   print DLLDB $spec_details->{DLL_NAME},"\n";
1184   close(DLLDB);
1185
1186   if ($opt_output_format eq "s")
1187   {
1188     output_sgml_dll_file($spec_details);
1189     return;
1190   }
1191 }
1192
1193 #
1194 # OUTPUT FUNCTIONS
1195 # ----------------
1196 # Only these functions know anything about formatting for a specific
1197 # output type. The functions above work only with plain text.
1198 # This is to allow new types of output to be added easily.
1199
1200 # Open the api file
1201 sub output_open_api_file
1202 {
1203   my $output_name = shift(@_);
1204   $output_name = $opt_output_directory."/".$output_name;
1205
1206   if ($opt_output_format eq "h")
1207   {
1208     $output_name = $output_name.".html";
1209   }
1210   elsif ($opt_output_format eq "s")
1211   {
1212     $output_name = $output_name.".sgml";
1213   }
1214   else
1215   {
1216     $output_name = $output_name.".".$opt_manual_section;
1217   }
1218   open(OUTPUT,">$output_name") || die "Couldn't create file '$output_name'\n";
1219 }
1220
1221 # Close the api file
1222 sub output_close_api_file
1223 {
1224   close (OUTPUT);
1225 }
1226
1227 # Output the api file header
1228 sub output_api_header
1229 {
1230   my $comment = shift(@_);
1231
1232   if ($opt_output_format eq "h")
1233   {
1234       print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1235       print OUTPUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
1236       print OUTPUT "<HTML>\n<HEAD>\n";
1237       print OUTPUT "<LINK REL=\"StyleSheet\" href=\"apidoc.css\" type=\"text/css\">\n";
1238       print OUTPUT "<META NAME=\"GENERATOR\" CONTENT=\"tools/c2man.pl\">\n";
1239       print OUTPUT "<META NAME=\"keywords\" CONTENT=\"Win32,Wine,API,$comment->{COMMENT_NAME}\">\n";
1240       print OUTPUT "<TITLE>Wine API: $comment->{COMMENT_NAME}</TITLE>\n</HEAD>\n<BODY>\n";
1241   }
1242   elsif ($opt_output_format eq "s")
1243   {
1244       print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n",
1245                    "<sect1>\n",
1246                    "<title>$comment->{COMMENT_NAME}</title>\n";
1247   }
1248   else
1249   {
1250       print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n".
1251                    ".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"".
1252                    "Wine API\" \"Wine API\"\n";
1253   }
1254 }
1255
1256 sub output_api_footer
1257 {
1258   if ($opt_output_format eq "h")
1259   {
1260       print OUTPUT "<hr><p><i class=\"copy\">Copyright &copy ".$year." The Wine Project.".
1261                    "Visit <a HREF=http://www.winehq.org>WineHQ</a> for license details.".
1262                    "Generated $date</i></p>\n</body>\n</html>\n";
1263   }
1264   elsif ($opt_output_format eq "s")
1265   {
1266       print OUTPUT "</sect1>\n";
1267       return;
1268   }
1269   else
1270   {
1271   }
1272 }
1273
1274 sub output_api_section_start
1275 {
1276   my $comment = shift(@_);
1277   my $section_name = shift(@_);
1278
1279   if ($opt_output_format eq "h")
1280   {
1281     print OUTPUT "\n<p><h2 class=\"section\">",$section_name,"</h2></p>\n";
1282   }
1283   elsif ($opt_output_format eq "s")
1284   {
1285     print OUTPUT "<bridgehead>",$section_name,"</bridgehead>\n";
1286   }
1287   else
1288   {
1289     print OUTPUT "\n\.SH ",$section_name,"\n";
1290   }
1291 }
1292
1293 sub output_api_section_end
1294 {
1295   # Not currently required by any output formats
1296 }
1297
1298 sub output_api_name
1299 {
1300   my $comment = shift(@_);
1301
1302   output_api_section_start($comment,"NAME");
1303
1304   my $dll_ordinal = "";
1305   if ($comment->{ORDINAL} ne "")
1306   {
1307     $dll_ordinal = "(".$comment->{DLL_NAME}.".".$comment->{ORDINAL}.")";
1308   }
1309   if ($opt_output_format eq "h")
1310   {
1311     print OUTPUT "<p><b class=\"func_name\">",$comment->{COMMENT_NAME},
1312                  "</b>&nbsp;&nbsp;<i class=\"dll_ord\">",
1313                  ,$dll_ordinal,"</i></p>\n";
1314   }
1315   elsif ($opt_output_format eq "s")
1316   {
1317     print OUTPUT "<para>\n  <command>",$comment->{COMMENT_NAME},"</command>  <emphasis>",
1318                  $dll_ordinal,"</emphasis>\n</para>\n";
1319   }
1320   else
1321   {
1322     print OUTPUT "\\fB",$comment->{COMMENT_NAME},"\\fR ",$dll_ordinal;
1323   }
1324
1325   output_api_section_end();
1326 }
1327
1328 sub output_api_synopsis
1329 {
1330   my $comment = shift(@_);
1331   my @fmt;
1332
1333   output_api_section_start($comment,"SYNOPSIS");
1334
1335   if ($opt_output_format eq "h")
1336   {
1337     print OUTPUT "<p><pre class=\"proto\">\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1338     @fmt = ("", "\n", "<tt class=\"param\">", "</tt>");
1339   }
1340   elsif ($opt_output_format eq "s")
1341   {
1342     print OUTPUT "<screen>\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1343     @fmt = ("", "\n", "<emphasis>", "</emphasis>");
1344   }
1345   else
1346   {
1347     print OUTPUT $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1348     @fmt = ("", "\n", "\\fI", "\\fR");
1349   }
1350
1351   # Since our prototype is output in a pre-formatted block, line up the
1352   # parameters and parameter comments in the same column.
1353
1354   # First caluculate where the columns should start
1355   my $biggest_length = 0;
1356   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1357   {
1358     my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1359     if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1360     {
1361       my $length = length $1;
1362       if ($length > $biggest_length)
1363       {
1364         $biggest_length = $length;
1365       }
1366     }
1367   }
1368
1369   # Now pad the string with blanks
1370   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1371   {
1372     my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1373     if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1374     {
1375       my $pad_len = $biggest_length - length $1;
1376       my $padding = " " x ($pad_len);
1377       ${@{$comment->{PROTOTYPE}}}[$i] = $1.$padding.$2;
1378     }
1379   }
1380
1381   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1382   {
1383     # Format the parameter name
1384     my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1385     my $comma = ($i == @{$comment->{PROTOTYPE}}-1) ? "" : ",";
1386     $line =~ s/(.+?)([A-Za-z_][A-Za-z_0-9]*)$/  $fmt[0]$1$fmt[2]$2$fmt[3]$comma$fmt[1]/;
1387     print OUTPUT $line;
1388   }
1389
1390   if ($opt_output_format eq "h")
1391   {
1392     print OUTPUT " )\n\n</pre></p>\n";
1393   }
1394   elsif ($opt_output_format eq "s")
1395   {
1396     print OUTPUT " )\n</screen>\n";
1397   }
1398   else
1399   {
1400     print OUTPUT " )\n";
1401   }
1402
1403   output_api_section_end();
1404 }
1405
1406 sub output_api_comment
1407 {
1408   my $comment = shift(@_);
1409   my $open_paragraph = 0;
1410   my $open_raw = 0;
1411   my $param_docs = 0;
1412   my @fmt;
1413
1414   if ($opt_output_format eq "h")
1415   {
1416     @fmt = ("<p>", "</p>\n", "<tt class=\"const\">", "</tt>", "<b class=\"emp\">", "</b>",
1417             "<tt class=\"coderef\">", "</tt>", "<tt class=\"param\">", "</tt>",
1418             "<i class=\"in_out\">", "</i>", "<pre class=\"raw\">\n", "</pre>\n",
1419             "<table class=\"tab\"><colgroup><col><col><col></colgroup><tbody>\n",
1420             "</tbody></table>\n","<tr><td>","</td></tr>\n","</td>","</td><td>");
1421   }
1422   elsif ($opt_output_format eq "s")
1423   {
1424     @fmt = ("<para>\n","\n</para>\n","<constant>","</constant>","<emphasis>","</emphasis>",
1425             "<command>","</command>","<constant>","</constant>","<emphasis>","</emphasis>",
1426             "<screen>\n","</screen>\n",
1427             "<informaltable frame=\"none\">\n<tgroup cols=\"3\">\n<tbody>\n",
1428             "</tbody>\n</tgroup>\n</informaltable>\n","<row><entry>","</entry></row>\n",
1429             "</entry>","</entry><entry>");
1430   }
1431   else
1432   {
1433     @fmt = ("\.PP\n", "\n", "\\fB", "\\fR", "\\fB", "\\fR", "\\fB", "\\fR", "\\fI", "\\fR",
1434             "\\fB", "\\fR ", "", "", "", "","","\n.PP\n","","");
1435   }
1436
1437   # Extract the parameter names
1438   my @parameter_names;
1439   for (@{$comment->{PROTOTYPE}})
1440   {
1441     if ( /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ )
1442     {
1443       push (@parameter_names, $2);
1444     }
1445   }
1446
1447   for (@{$comment->{TEXT}})
1448   {
1449     if ($opt_output_format eq "h" || $opt_output_format eq "s")
1450     {
1451       # Map special characters
1452       s/\&/\&amp;/g;
1453       s/\</\&lt;/g;
1454       s/\>/\&gt;/g;
1455       s/\([Cc]\)/\&copy;/g;
1456       s/\(tm\)/&#174;/;
1457     }
1458
1459     if ( s/^\|// )
1460     {
1461       # Raw output
1462       if ($open_raw == 0)
1463       {
1464         if ($open_paragraph == 1)
1465         {
1466           # Close the open paragraph
1467           print OUTPUT $fmt[1];
1468           $open_paragraph = 0;
1469         }
1470         # Start raw output
1471         print OUTPUT $fmt[12];
1472         $open_raw = 1;
1473       }
1474       if ($opt_output_format eq "")
1475       {
1476         print OUTPUT ".br\n"; # Prevent 'man' running these lines together
1477       }
1478       print OUTPUT $_,"\n";
1479     }
1480     else
1481     {
1482       # Highlight strings
1483       s/(\".+?\")/$fmt[2]$1$fmt[3]/g;
1484       # Highlight literal chars
1485       s/(\'.\')/$fmt[2]$1$fmt[3]/g;
1486       s/(\'.{2}\')/$fmt[2]$1$fmt[3]/g;
1487       # Highlight numeric constants
1488       s/( |\-|\+|\.|\()([0-9\-\.]+)( |\-|$|\.|\,|\*|\?|\))/$1$fmt[2]$2$fmt[3]$3/g;
1489
1490       # Leading cases ("xxxx:","-") start new paragraphs & are emphasised
1491       # FIXME: Using bullet points for leading '-' would look nicer.
1492       if ($open_paragraph == 1)
1493       {
1494         s/^(\-)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1495         s/^([[A-Za-z\-]+\:)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1496       }
1497       else
1498       {
1499         s/^(\-)/$fmt[4]$1$fmt[5]/;
1500         s/^([[A-Za-z\-]+\:)/$fmt[4]$1$fmt[5]/;
1501       }
1502
1503       if ($opt_output_format eq "h")
1504       {
1505         # Html uses links for API calls
1506         s/([A-Za-z_]+[A-Za-z_0-9]+)(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1507         # And references to COM objects (hey, they'll get documented one day)
1508         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g;
1509         # Convert any web addresses to real links
1510         s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g;
1511       }
1512       else
1513       {
1514         if ($opt_output_format eq "")
1515         {
1516           # Give the man section for API calls
1517           s/ ([A-Za-z_]+[A-Za-z_0-9]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g;
1518         }
1519         else
1520         {
1521           # Highlight API calls
1522           s/ ([A-Za-z_]+[A-Za-z_0-9]+\(\))/ $fmt[6]$1$fmt[7]/g;
1523         }
1524
1525         # And references to COM objects
1526         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g;
1527       }
1528
1529       if ($open_raw == 1)
1530       {
1531         # Finish the raw output
1532         print OUTPUT $fmt[13];
1533         $open_raw = 0;
1534       }
1535
1536       if ( /^[A-Z]+$/ || /^SEE ALSO$/ )
1537       {
1538         # Start of a new section
1539         if ($open_paragraph == 1)
1540         {
1541           if ($param_docs == 1)
1542           {
1543             print OUTPUT $fmt[17],$fmt[15];
1544           }
1545           else
1546           {
1547             print OUTPUT $fmt[1];
1548           }
1549           $open_paragraph = 0;
1550         }
1551         output_api_section_start($comment,$_);
1552         if ( /^PARAMS$/ )
1553         {
1554           print OUTPUT $fmt[14];
1555           $param_docs = 1;
1556         }
1557         else
1558         {
1559           #print OUTPUT $fmt[15];
1560           $param_docs = 0;
1561         }
1562       }
1563       elsif ( /^$/ )
1564       {
1565         # Empty line, indicating a new paragraph
1566         if ($open_paragraph == 1)
1567         {
1568           if ($param_docs == 0)
1569           {
1570             print OUTPUT $fmt[1];
1571             $open_paragraph = 0;
1572           }
1573         }
1574       }
1575       else
1576       {
1577         if ($param_docs == 1)
1578         {
1579           if ($open_paragraph == 1)
1580           {
1581             # For parameter docs, put each parameter into a new paragraph/table row
1582             print OUTPUT $fmt[17];
1583             $open_paragraph = 0;
1584           }
1585           s/(\[.+\])( *)/$fmt[19]$fmt[10]$1$fmt[11]$fmt[19]/; # Format In/Out
1586         }
1587         else
1588         {
1589           # Within paragraph lines, prevent lines running together
1590           $_ = $_." ";
1591         }
1592
1593         # Format parameter names where they appear in the comment
1594         for my $parameter_name (@parameter_names)
1595         {
1596           s/(^|[ \.\,\(\-])($parameter_name)($|[ \.\)\,\-])/$1$fmt[8]$2$fmt[9]$3/g;
1597         }
1598
1599         if ($open_paragraph == 0)
1600         {
1601           if ($param_docs == 1)
1602           {
1603             print OUTPUT $fmt[16];
1604           }
1605           else
1606           {
1607             print OUTPUT $fmt[0];
1608           }
1609           $open_paragraph = 1;
1610         }
1611         # Anything in all uppercase on its own gets emphasised
1612         s/(^|[ \.\,\(\[\|\=])([A-Z]+?[A-Z0-9_]+)($|[ \.\,\*\?\|\)\=\'])/$1$fmt[6]$2$fmt[7]$3/g;
1613
1614         print OUTPUT $_;
1615       }
1616     }
1617   }
1618   if ($open_raw == 1)
1619   {
1620     print OUTPUT $fmt[13];
1621   }
1622   if ($open_paragraph == 1)
1623   {
1624     print OUTPUT $fmt[1];
1625   }
1626 }
1627
1628 # Create the master index file
1629 sub output_master_index_files
1630 {
1631   if ($opt_output_format eq "")
1632   {
1633     return; # No master index for man pages
1634   }
1635
1636   # Use the comment output functions for consistency
1637   my $comment =
1638   {
1639     FILE => "",
1640     COMMENT_NAME => "The Wine Api Guide",
1641     ALT_NAME => "The Wine Api Guide",
1642     DLL_NAME => "",
1643     ORDINAL => "",
1644     RETURNS => "",
1645     PROTOTYPE => [],
1646     TEXT => [],
1647   };
1648
1649   if ($opt_output_format eq "s")
1650   {
1651     $comment->{COMMENT_NAME} = "Introduction";
1652     $comment->{ALT_NAME} = "Introduction",
1653   }
1654   elsif ($opt_output_format eq "h")
1655   {
1656     @{$comment->{TEXT}} = (
1657       "NAME",
1658        $comment->{COMMENT_NAME},
1659        "INTRODUCTION",
1660     );
1661   }
1662
1663   # Create the initial comment text
1664   push (@{$comment->{TEXT}},
1665     "This document describes the Api calls made available",
1666     "by Wine. They are grouped by the dll that exports them.",
1667     "",
1668     "Please do not edit this document, since it is generated automatically",
1669     "from the Wine source code tree. Details on updating this documentation",
1670     "are given in the \"Wine Developers Guide\".",
1671     "CONTRIBUTORS",
1672     "Api documentation is generally written by the person who ",
1673     "implements a given Api call. Authors of each dll are listed in the overview ",
1674     "section for that dll. Additional contributors who have updated source files ",
1675     "but have not entered their names in a copyright statement are noted by an ",
1676     "entry in the file \"Changelog\" from the Wine source code distribution.",
1677       ""
1678   );
1679
1680   # Read in all dlls from the database of dll names
1681   my $input_file = $opt_output_directory."/dlls.db";
1682   my @dlls = `cat $input_file|sort|uniq`;
1683
1684   if ($opt_output_format eq "h")
1685   {
1686     # HTML gets a list of all the dlls. For docbook the index creates this for us
1687     push (@{$comment->{TEXT}},
1688       "DLLS",
1689       "The following dlls are provided by Wine:",
1690       ""
1691     );
1692     # Add the dlls to the comment
1693     for (@dlls)
1694     {
1695       $_ =~ s/(\..*)?\n/\(\)/;
1696       push (@{$comment->{TEXT}}, $_, "");
1697     }
1698     output_open_api_file("index");
1699   }
1700   elsif ($opt_output_format eq "s")
1701   {
1702     # Just write this as the initial blurb, with a chapter heading
1703     output_open_api_file("blurb");
1704     print OUTPUT "<chapter id =\"blurb\">\n<title>Introduction to The Wine Api Guide</title>\n"
1705   }
1706
1707   # Write out the document
1708   output_api_header($comment);
1709   output_api_comment($comment);
1710   output_api_footer($comment);
1711   if ($opt_output_format eq "s")
1712   {
1713     print OUTPUT "</chapter>\n" # finish the chapter
1714   }
1715   output_close_api_file();
1716
1717   if ($opt_output_format eq "s")
1718   {
1719     output_sgml_master_file(\@dlls);
1720     return;
1721   }
1722   if ($opt_output_format eq "h")
1723   {
1724     output_html_stylesheet();
1725     # FIXME: Create an alphabetical index
1726     return;
1727   }
1728 }
1729
1730 # Write the master wine-api.sgml, linking it to each dll.
1731 sub output_sgml_master_file
1732 {
1733   my $dlls = shift(@_);
1734
1735   output_open_api_file("wine-api");
1736   print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1737   print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n";
1738   print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n";
1739
1740   # List the entities
1741   for (@$dlls)
1742   {
1743     $_ =~ s/(\..*)?\n//;
1744     print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n"
1745   }
1746
1747   print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine Api Guide</title></bookinfo>\n\n";
1748   print OUTPUT "  &blurb;\n";
1749
1750   for (@$dlls)
1751   {
1752     print OUTPUT "  &",$_,";\n"
1753   }
1754   print OUTPUT "\n\n</book>\n";
1755
1756   output_close_api_file();
1757 }
1758
1759 # Produce the sgml for the dll chapter from the generated files
1760 sub output_sgml_dll_file
1761 {
1762   my $spec_details = shift(@_);
1763
1764   # Make a list of all the documentation files to include
1765   my $exports = $spec_details->{EXPORTS};
1766   my @source_files = ();
1767   for (@$exports)
1768   {
1769     # @$_ => ordinal, call convention, exported name, implementation name, documented;
1770     if (@$_[1] ne "forward" && @$_[1] ne "extern" && @$_[1] ne "stub" && @$_[1] ne "equate" &&
1771         @$_[1] ne "variable" && @$_[1] ne "fake" && @$_[4] & 1)
1772     {
1773       # A documented function
1774       push (@source_files,@$_[3]);
1775     }
1776   }
1777
1778   push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
1779
1780   @source_files = sort @source_files;
1781
1782   # create a new chapter for this dll
1783   my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
1784   open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
1785   print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
1786   output_close_api_file();
1787
1788   # Add the sorted documentation, cleaning up as we go
1789   `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`;
1790   for (@source_files)
1791   {
1792     `cat $opt_output_directory/$_.sgml >>$tmp_name`;
1793     `rm -f $opt_output_directory/$_.sgml`;
1794   }
1795
1796   # close the chapter, and overwite the dll source
1797   open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
1798   print OUTPUT "</chapter>\n";
1799   close OUTPUT;
1800   `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`;
1801 }
1802
1803 # Output the stylesheet for HTML output
1804 sub output_html_stylesheet
1805 {
1806   if ($opt_output_format ne "h")
1807   {
1808     return;
1809   }
1810
1811   my $css;
1812   ($css = <<HERE_TARGET) =~ s/^\s+//gm;
1813 /*
1814  * Default styles for Wine HTML Documentation.
1815  *
1816  * This style sheet should be altered to suit your needs/taste.
1817  */
1818 BODY { /* Page body */
1819 background-color: white;
1820 color: black;
1821 font-family: Tahomsans-serif;
1822 font-style: normal;
1823 font-size: 10pt;
1824 }
1825 a:link { color: #4444ff; } /* Links */
1826 a:visited { color: #333377 }
1827 a:active { color: #0000dd }
1828 H2.section { /* Section Headers */
1829 font-family: sans-serif;
1830 color: #777777;
1831 background-color: #F0F0FE;
1832 margin-left: 0.2in;
1833 margin-right: 1.0in;
1834 }
1835 b.func_name { /* Function Name */
1836 font-size: 10pt;
1837 font-style: bold;
1838 }
1839 i.dll_ord { /* Italicised DLL+ordinal */
1840 color: #888888;
1841 font-family: sans-serif;
1842 font-size: 8pt;
1843 }
1844 p { /* Paragraphs */
1845 margin-left: 0.5in;
1846 margin-right: 0.5in;
1847 }
1848 table { /* tables */
1849 margin-left: 0.5in;
1850 margin-right: 0.5in;
1851 }
1852 pre.proto /* API Function prototype */
1853 {
1854 border-style: solid;
1855 border-width: 1px;
1856 border-color: #777777;
1857 background-color: #F0F0BB;
1858 color: black;
1859 font-size: 10pt;
1860 vertical-align: top;
1861 margin-left: 0.5in;
1862 margin-right: 1.0in;
1863 }
1864 tt.param { /* Parameter name */
1865 font-style: italic;
1866 color: blue;
1867 }
1868 tt.const { /* Constant */
1869 color: red;
1870 }
1871 i.in_out { /* In/Out */
1872 font-size: 8pt;
1873 color: grey;
1874 }
1875 tt.coderef { /* Code in description text */
1876 color: darkgreen;
1877 }
1878 b.emp /* Emphasis */ {
1879 font-style: bold;
1880 color: darkblue;
1881 }
1882 i.footer { /* Footer */
1883 font-family: sans-serif;
1884 font-size: 6pt;
1885 color: darkgrey;
1886 }
1887 HERE_TARGET
1888
1889   my $output_file = "$opt_output_directory/apidoc.css";
1890   open(CSS,">$output_file") || die "Couldn't create the file $output_file\n";
1891   print CSS $css;
1892   close(CSS);
1893 }