regedit: Some minor unicode conversions.
[wine.git] / tools / c2man.pl
blob4272b734c68b1f29d3ae4100140c702d6b799c91
1 #! /usr/bin/perl -w
3 # Generate API documentation. See documentation/documentation.sgml for details.
5 # Copyright (C) 2000 Mike McCormack
6 # Copyright (C) 2003 Jon Griffiths
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.
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.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 # TODO
23 # Consolidate A+W pairs together, and only write one doc, without the suffix
24 # Implement automatic docs fo structs/defines in headers
25 # SGML gurus - feel free to smarten up the SGML.
26 # Add any other relevant information for the dll - imports etc
27 # Should we have a special output mode for WineHQ?
29 use strict;
30 use bytes;
32 # Function flags. most of these come from the spec flags
33 my $FLAG_DOCUMENTED = 1;
34 my $FLAG_NONAME = 2;
35 my $FLAG_I386 = 4;
36 my $FLAG_REGISTER = 8;
37 my $FLAG_APAIR = 16; # The A version of a matching W function
38 my $FLAG_WPAIR = 32; # The W version of a matching A function
39 my $FLAG_64PAIR = 64; # The 64 bit version of a matching 32 bit function
42 # Options
43 my $opt_output_directory = "man3w"; # All default options are for nroff (man pages)
44 my $opt_manual_section = "3w";
45 my $opt_source_dir = "";
46 my $opt_wine_root_dir = "";
47 my $opt_output_format = ""; # '' = nroff, 'h' = html, 's' = sgml
48 my $opt_output_empty = 0; # Non-zero = Create 'empty' comments (for every implemented function)
49 my $opt_fussy = 1; # Non-zero = Create only if we have a RETURNS section
50 my $opt_verbose = 0; # >0 = verbosity. Can be given multiple times (for debugging)
51 my @opt_header_file_list = ();
52 my @opt_spec_file_list = ();
53 my @opt_source_file_list = ();
55 # All the collected details about all the .spec files being processed
56 my %spec_files;
57 # All the collected details about all the source files being processed
58 my %source_files;
59 # All documented functions that are to be placed in the index
60 my @index_entries_list = ();
62 # useful globals
63 my $pwd = `pwd`."/";
64 $pwd =~ s/\n//;
65 my @datetime = localtime;
66 my @months = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
67 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
68 my $year = $datetime[5] + 1900;
69 my $date = "$months[$datetime[4]] $year";
72 sub output_api_comment($);
73 sub output_api_footer($);
74 sub output_api_header($);
75 sub output_api_name($);
76 sub output_api_synopsis($);
77 sub output_close_api_file();
78 sub output_comment($);
79 sub output_html_index_files();
80 sub output_html_stylesheet();
81 sub output_open_api_file($);
82 sub output_sgml_dll_file($);
83 sub output_sgml_master_file($);
84 sub output_spec($);
85 sub process_comment($);
86 sub process_extra_comment($);
89 # Generate the list of exported entries for the dll
90 sub process_spec_file($)
92 my $spec_name = shift;
93 my ($dll_name, $dll_ext) = split(/\./, $spec_name);
94 $dll_ext = "dll" if ( $dll_ext eq "spec" );
95 my $uc_dll_name = uc $dll_name;
97 my $spec_details =
99 NAME => $spec_name,
100 DLL_NAME => $dll_name,
101 DLL_EXT => $dll_ext,
102 NUM_EXPORTS => 0,
103 NUM_STUBS => 0,
104 NUM_FUNCS => 0,
105 NUM_FORWARDS => 0,
106 NUM_VARS => 0,
107 NUM_DOCS => 0,
108 CONTRIBUTORS => [ ],
109 SOURCES => [ ],
110 DESCRIPTION => [ ],
111 EXPORTS => [ ],
112 EXPORTED_NAMES => { },
113 IMPLEMENTATION_NAMES => { },
114 EXTRA_COMMENTS => [ ],
115 CURRENT_EXTRA => [ ] ,
118 if ($opt_verbose > 0)
120 print "Processing ".$spec_name."\n";
123 # We allow opening to fail just to cater for the peculiarities of
124 # the Wine build system. This doesn't hurt, in any case
125 open(SPEC_FILE, "<$spec_name")
126 || (($opt_source_dir ne "")
127 && open(SPEC_FILE, "<$opt_source_dir/$spec_name"))
128 || return;
130 while(<SPEC_FILE>)
132 s/^\s+//; # Strip leading space
133 s/\s+\n$/\n/; # Strip trailing space
134 s/\s+/ /g; # Strip multiple tabs & spaces to a single space
135 s/\s*#.*//; # Strip comments
136 s/\(.*\)/ /; # Strip arguments
137 s/\s+/ /g; # Strip multiple tabs & spaces to a single space (again)
138 s/\n$//; # Strip newline
140 my $flags = 0;
141 if( /\-noname/ )
143 $flags |= $FLAG_NONAME;
145 if( /\-i386/ )
147 $flags |= $FLAG_I386;
149 if( /\-register/ )
151 $flags |= $FLAG_REGISTER;
153 s/ \-[a-z0-9]+//g; # Strip flags
155 if( /^(([0-9]+)|@) / )
157 # This line contains an exported symbol
158 my ($ordinal, $call_convention, $exported_name, $implementation_name) = split(' ');
160 for ($call_convention)
162 /^(cdecl|stdcall|varargs|pascal)$/
163 && do { $spec_details->{NUM_FUNCS}++; last; };
164 /^(variable|equate)$/
165 && do { $spec_details->{NUM_VARS}++; last; };
166 /^(extern)$/
167 && do { $spec_details->{NUM_FORWARDS}++; last; };
168 /^stub$/ && do { $spec_details->{NUM_STUBS}++; last; };
169 if ($opt_verbose > 0)
171 print "Warning: didn't recognise convention \'",$call_convention,"'\n";
173 last;
176 # Convert ordinal only names so we can find them later
177 if ($exported_name eq "@")
179 $exported_name = $uc_dll_name.'_'.$ordinal;
181 if (!defined($implementation_name))
183 $implementation_name = $exported_name;
185 if ($implementation_name eq "")
187 $implementation_name = $exported_name;
190 if ($implementation_name =~ /(.*?)\./)
192 $call_convention = "forward"; # Referencing a function from another dll
193 $spec_details->{NUM_FUNCS}--;
194 $spec_details->{NUM_FORWARDS}++;
197 # Add indices for the exported and implementation names
198 $spec_details->{EXPORTED_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
199 if ($implementation_name ne $exported_name)
201 $spec_details->{IMPLEMENTATION_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
204 # Add the exported entry
205 $spec_details->{NUM_EXPORTS}++;
206 my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $flags);
207 push (@{$spec_details->{EXPORTS}},[@export]);
210 close(SPEC_FILE);
212 # Add this .spec files details to the list of .spec files
213 $spec_files{$uc_dll_name} = [$spec_details];
216 # Read each source file, extract comments, and generate API documentation if appropriate
217 sub process_source_file($)
219 my $source_file = shift;
220 my $source_details =
222 CONTRIBUTORS => [ ],
223 DEBUG_CHANNEL => "",
225 my $comment =
227 FILE => $source_file,
228 COMMENT_NAME => "",
229 ALT_NAME => "",
230 DLL_NAME => "",
231 DLL_EXT => "",
232 ORDINAL => "",
233 RETURNS => "",
234 PROTOTYPE => [],
235 TEXT => [],
237 my $parse_state = 0;
238 my $ignore_blank_lines = 1;
239 my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export
241 if ($opt_verbose > 0)
243 print "Processing ".$source_file."\n";
245 open(SOURCE_FILE,"<$source_file")
246 || (($opt_source_dir ne "")
247 && open(SOURCE_FILE,"<$opt_source_dir/$source_file"))
248 || die "couldn't open ".$source_file."\n";
250 # Add this source file to the list of source files
251 $source_files{$source_file} = [$source_details];
253 while(<SOURCE_FILE>)
255 s/\n$//; # Strip newline
256 s/^\s+//; # Strip leading space
257 s/\s+$//; # Strip trailing space
258 if (! /^\*\|/ )
260 # Strip multiple tabs & spaces to a single space
261 s/\s+/ /g;
264 if ( / +Copyright *(\([Cc]\))*[0-9 \-\,\/]*([[:alpha:][:^ascii:] \.\-]+)/ )
266 # Extract a contributor to this file
267 my $contributor = $2;
268 $contributor =~ s/ *$//;
269 $contributor =~ s/^by //;
270 $contributor =~ s/\.$//;
271 $contributor =~ s/ (for .*)/ \($1\)/;
272 if ($contributor ne "")
274 if ($opt_verbose > 3)
276 print "Info: Found contributor:'".$contributor."'\n";
278 push (@{$source_details->{CONTRIBUTORS}},$contributor);
281 elsif ( /WINE_DEFAULT_DEBUG_CHANNEL\(([A-Za-z]*)\)/ )
283 # Extract the debug channel to use
284 if ($opt_verbose > 3)
286 print "Info: Found debug channel:'".$1."'\n";
288 $source_details->{DEBUG_CHANNEL} = $1;
291 if ($parse_state == 0) # Searching for a comment
293 if ( /^\/\**$/ )
295 # Found a comment start
296 $comment->{COMMENT_NAME} = "";
297 $comment->{ALT_NAME} = "";
298 $comment->{DLL_NAME} = "";
299 $comment->{ORDINAL} = "";
300 $comment->{RETURNS} = "";
301 $comment->{PROTOTYPE} = [];
302 $comment->{TEXT} = [];
303 $ignore_blank_lines = 1;
304 $extra_comment = 0;
305 $parse_state = 3;
308 elsif ($parse_state == 1) # Reading in a comment
310 if ( /^\**\// )
312 # Found the end of the comment
313 $parse_state = 2;
315 elsif ( s/^\*\|/\|/ )
317 # A line of comment not meant to be pre-processed
318 push (@{$comment->{TEXT}},$_); # Add the comment text
320 elsif ( s/^ *\** *// )
322 # A line of comment, starting with an asterisk
323 if ( /^[A-Z]+$/ || $_ eq "")
325 # This is a section start, so skip blank lines before and after it.
326 my $last_line = pop(@{$comment->{TEXT}});
327 if (defined($last_line) && $last_line ne "")
329 # Put it back
330 push (@{$comment->{TEXT}},$last_line);
332 if ( /^[A-Z]+$/ )
334 $ignore_blank_lines = 1;
336 else
338 $ignore_blank_lines = 0;
342 if ($ignore_blank_lines == 0 || $_ ne "")
344 push (@{$comment->{TEXT}},$_); # Add the comment text
347 else
349 # This isn't a well formatted comment: look for the next one
350 $parse_state = 0;
353 elsif ($parse_state == 2) # Finished reading in a comment
355 if ( /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ ||
356 /.*?\(/ )
358 # Comment is followed by a function definition
359 $parse_state = 4; # Fall through to read prototype
361 else
363 # Allow cpp directives and blank lines between the comment and prototype
364 if ($extra_comment == 1)
366 # An extra comment not followed by a function definition
367 $parse_state = 5; # Fall through to process comment
369 elsif (!/^\#/ && !/^ *$/ && !/^__ASM_GLOBAL_FUNC/)
371 # This isn't a well formatted comment: look for the next one
372 if ($opt_verbose > 1)
374 print "Info: Function '",$comment->{COMMENT_NAME},"' not followed by prototype.\n";
376 $parse_state = 0;
380 elsif ($parse_state == 3) # Reading in the first line of a comment
382 s/^ *\** *//;
383 if ( /^([\@A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])\s*(.*)$/ )
385 # Found a correctly formed "ApiName (DLLNAME.Ordinal)" line.
386 if (defined ($7) && $7 ne "")
388 push (@{$comment->{TEXT}},$_); # Add the trailing comment text
390 $comment->{COMMENT_NAME} = $1;
391 $comment->{DLL_NAME} = uc $3;
392 $comment->{ORDINAL} = $4;
393 $comment->{DLL_NAME} =~ s/^KERNEL$/KRNL386/; # Too many of these to ignore, _old_ code
394 $parse_state = 1;
396 elsif ( /^([A-Za-z0-9_-]+) +\{([A-Za-z0-9_]+)\}$/ )
398 # Found a correctly formed "CommentTitle {DLLNAME}" line (extra documentation)
399 $comment->{COMMENT_NAME} = $1;
400 $comment->{DLL_NAME} = uc $2;
401 $comment->{ORDINAL} = "";
402 $extra_comment = 1;
403 $parse_state = 1;
405 else
407 # This isn't a well formatted comment: look for the next one
408 $parse_state = 0;
412 if ($parse_state == 4) # Reading in the function definition
414 push (@{$comment->{PROTOTYPE}},$_);
415 # Strip comments from the line before checking for ')'
416 my $stripped_line = $_;
417 $stripped_line =~ s/ *(\/\* *)(.*?)( *\*\/ *)//;
418 if ( $stripped_line =~ /\)/ )
420 # Strip a blank last line
421 my $last_line = pop(@{$comment->{TEXT}});
422 if (defined($last_line) && $last_line ne "")
424 # Put it back
425 push (@{$comment->{TEXT}},$last_line);
428 if ($opt_output_empty != 0 && @{$comment->{TEXT}} == 0)
430 # Create a 'not implemented' comment
431 @{$comment->{TEXT}} = ("fixme: This function has not yet been documented.");
433 $parse_state = 5;
437 if ($parse_state == 5) # Processing the comment
439 # Process it, if it has any text
440 if (@{$comment->{TEXT}} > 0)
442 if ($extra_comment == 1)
444 process_extra_comment($comment);
446 else
448 @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
449 process_comment($comment);
452 elsif ($opt_verbose > 1 && $opt_output_empty == 0)
454 print "Info: Function '",$comment->{COMMENT_NAME},"' has no documentation.\n";
456 $parse_state = 0;
459 close(SOURCE_FILE);
462 # Standardise a comments text for consistency
463 sub process_comment_text($)
465 my $comment = shift;
466 my $in_params = 0;
467 my @tmp_list = ();
468 my $i = 0;
470 for (@{$comment->{TEXT}})
472 my $line = $_;
474 if ( /^\s*$/ || /^[A-Z]+$/ || /^-/ )
476 $in_params = 0;
478 if ( $in_params > 0 && !/\[/ && !/\]/ )
480 # Possibly a continuation of the parameter description
481 my $last_line = pop(@tmp_list);
482 if ( $last_line =~ /\[/ && $last_line =~ /\]/ )
484 $line = $last_line." ".$_;
486 else
488 $in_params = 0;
489 push (@tmp_list, $last_line);
492 if ( /^(PARAMS|MEMBERS)$/ )
494 $in_params = 1;
496 push (@tmp_list, $line);
499 @{$comment->{TEXT}} = @tmp_list;
501 for (@{$comment->{TEXT}})
503 if (! /^\|/ )
505 # Map I/O values. These come in too many formats to standardise now....
506 s/\[I\]|\[i\]|\[in\]|\[IN\]/\[In\] /g;
507 s/\[O\]|\[o\]|\[out\]|\[OUT\]/\[Out\]/g;
508 s/\[I\/O\]|\[I\,O\]|\[i\/o\]|\[in\/out\]|\[IN\/OUT\]/\[In\/Out\]/g;
509 # TRUE/FALSE/NULL are defines, capitilise them
510 s/True|true/TRUE/g;
511 s/False|false/FALSE/g;
512 s/Null|null/NULL/g;
513 # Preferred capitalisations
514 s/ wine| WINE/ Wine/g;
515 s/ API | api / Api /g;
516 s/ DLL | Dll / dll /g;
517 s/ URL | url / Url /g;
518 s/WIN16|win16/Win16/g;
519 s/WIN32|win32/Win32/g;
520 s/WIN64|win64/Win64/g;
521 s/ ID | id / Id /g;
522 # Grammar
523 s/([a-z])\.([A-Z])/$1\. $2/g; # Space after full stop
524 s/ \:/\:/g; # Colons to the left
525 s/ \;/\;/g; # Semi-colons too
526 # Common idioms
527 s/^See ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to A version from W
528 s/^Unicode version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Ditto
529 s/^64\-bit version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to 32 bit version from 64
530 s/^PARAMETERS$/PARAMS/; # Name of parameter section should be 'PARAMS'
531 # Trademarks
532 s/( |\.)(M\$|MS|Microsoft|microsoft|micro\$oft|Micro\$oft)( |\.)/$1Microsoft\(tm\)$3/g;
533 s/( |\.)(Windows|windows|windoze|winblows)( |\.)/$1Windows\(tm\)$3/g;
534 s/( |\.)(DOS|dos|msdos)( |\.)/$1MS-DOS\(tm\)$3/g;
535 s/( |\.)(UNIX|unix)( |\.)/$1Unix\(tm\)$3/g;
536 s/( |\.)(LINIX|linux)( |\.)/$1Linux\(tm\)$3/g;
537 # Abbreviations
538 s/( char )/ character /g;
539 s/( chars )/ characters /g;
540 s/( info )/ information /g;
541 s/( app )/ application /g;
542 s/( apps )/ applications /g;
543 s/( exe )/ executable /g;
544 s/( ptr )/ pointer /g;
545 s/( obj )/ object /g;
546 s/( err )/ error /g;
547 s/( bool )/ boolean /g;
548 s/( no\. )/ number /g;
549 s/( No\. )/ Number /g;
550 # Punctuation
551 if ( /\[I|\[O/ && ! /\.$/ )
553 $_ = $_."."; # Always have a full stop at the end of parameter desc.
555 elsif ($i > 0 && /^[A-Z]*$/ &&
556 !(@{$comment->{TEXT}}[$i-1] =~ /\.$/) &&
557 !(@{$comment->{TEXT}}[$i-1] =~ /\:$/))
560 if (!(@{$comment->{TEXT}}[$i-1] =~ /^[A-Z]*$/))
562 # Paragraphs always end with a full stop
563 @{$comment->{TEXT}}[$i-1] = @{$comment->{TEXT}}[$i-1].".";
567 $i++;
571 # Standardise our comment and output it if it is suitable.
572 sub process_comment($)
574 my $comment = shift;
576 # Don't process this comment if the function isn't exported
577 my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
579 if (!defined($spec_details))
581 if ($opt_verbose > 2)
583 print "Warning: Function '".$comment->{COMMENT_NAME}."' belongs to '".
584 $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
586 return;
589 if ($comment->{COMMENT_NAME} eq "@")
591 my $found = 0;
593 # Find the name from the .spec file
594 for (@{$spec_details->{EXPORTS}})
596 if (@$_[0] eq $comment->{ORDINAL})
598 $comment->{COMMENT_NAME} = @$_[2];
599 $found = 1;
603 if ($found == 0)
605 # Create an implementation name
606 $comment->{COMMENT_NAME} = $comment->{DLL_NAME}."_".$comment->{ORDINAL};
610 my $exported_names = $spec_details->{EXPORTED_NAMES};
611 my $export_index = $exported_names->{$comment->{COMMENT_NAME}};
612 my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES};
614 if (!defined($export_index))
616 # Perhaps the comment uses the implementation name?
617 $export_index = $implementation_names->{$comment->{COMMENT_NAME}};
619 if (!defined($export_index))
621 # This function doesn't appear to be exported. hmm.
622 if ($opt_verbose > 2)
624 print "Warning: Function '".$comment->{COMMENT_NAME}."' claims to belong to '".
625 $comment->{DLL_NAME}."' but is not exported by it: not processing it.\n";
627 return;
630 # When the function is exported twice we have the second name below the first
631 # (you see this a lot in ntdll, but also in some other places).
632 my $first_line = ${@{$comment->{TEXT}}}[1];
634 if ( $first_line =~ /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
636 # Found a second name - mark it as documented
637 my $alt_index = $exported_names->{$1};
638 if (defined($alt_index))
640 if ($opt_verbose > 2)
642 print "Info: Found alternate name '",$1,"\n";
644 my $alt_export = @{$spec_details->{EXPORTS}}[$alt_index];
645 @$alt_export[4] |= $FLAG_DOCUMENTED;
646 $spec_details->{NUM_DOCS}++;
647 ${@{$comment->{TEXT}}}[1] = "";
651 if (@{$spec_details->{CURRENT_EXTRA}})
653 # We have an extra comment that might be related to this one
654 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
655 my $current_name = $current_comment->{COMMENT_NAME};
656 if ($comment->{COMMENT_NAME} =~ /^$current_name/ && $comment->{COMMENT_NAME} ne $current_name)
658 if ($opt_verbose > 2)
660 print "Linking ",$comment->{COMMENT_NAME}," to $current_name\n";
662 # Add a reference to this comment to our extra comment
663 push (@{$current_comment->{TEXT}}, $comment->{COMMENT_NAME}."()","");
667 # We want our docs generated using the implementation name, so they are unique
668 my $export = @{$spec_details->{EXPORTS}}[$export_index];
669 $comment->{COMMENT_NAME} = @$export[3];
670 $comment->{ALT_NAME} = @$export[2];
672 # Mark the function as documented
673 $spec_details->{NUM_DOCS}++;
674 @$export[4] |= $FLAG_DOCUMENTED;
676 # This file is used by the DLL - Make sure we get our contributors right
677 push (@{$spec_details->{SOURCES}},$comment->{FILE});
679 # If we have parameter comments in the prototype, extract them
680 my @parameter_comments;
681 for (@{$comment->{PROTOTYPE}})
683 s/ *\, */\,/g; # Strip spaces from around commas
685 if ( s/ *(\/\* *)(.*?)( *\*\/ *)// ) # Strip out comment
687 my $parameter_comment = $2;
688 if (!$parameter_comment =~ /^\[/ )
690 # Add [IO] markers so we format the comment correctly
691 $parameter_comment = "[fixme] ".$parameter_comment;
693 if ( /( |\*)([A-Za-z_]{1}[A-Za-z_0-9]*)(\,|\))/ )
695 # Add the parameter name
696 $parameter_comment = $2." ".$parameter_comment;
698 push (@parameter_comments, $parameter_comment);
702 # If we extracted any prototype comments, add them to the comment text.
703 if (@parameter_comments)
705 @parameter_comments = ("PARAMS", @parameter_comments);
706 my @new_comment = ();
707 my $inserted_params = 0;
709 for (@{$comment->{TEXT}})
711 if ( $inserted_params == 0 && /^[A-Z]+$/ )
713 # Found a section header, so this is where we insert
714 push (@new_comment, @parameter_comments);
715 $inserted_params = 1;
717 push (@new_comment, $_);
719 if ($inserted_params == 0)
721 # Add them to the end
722 push (@new_comment, @parameter_comments);
724 $comment->{TEXT} = [@new_comment];
727 if ($opt_fussy == 1 && $opt_output_empty == 0)
729 # Reject any comment that doesn't have a description or a RETURNS section.
730 # This is the default for now, 'coz many comments aren't suitable.
731 my $found_returns = 0;
732 my $found_description_text = 0;
733 my $in_description = 0;
734 for (@{$comment->{TEXT}})
736 if ( /^RETURNS$/ )
738 $found_returns = 1;
739 $in_description = 0;
741 elsif ( /^DESCRIPTION$/ )
743 $in_description = 1;
745 elsif ($in_description == 1)
747 if ( !/^[A-Z]+$/ )
749 # Don't reject comments that refer to another doc (e.g. A/W)
750 if ( /^See ([A-Za-z0-9_]+)\.$/ )
752 if ($comment->{COMMENT_NAME} =~ /W$/ )
754 # This is probably a Unicode version of an Ascii function.
755 # Create the Ascii name and see if its been documented
756 my $ascii_name = $comment->{COMMENT_NAME};
757 $ascii_name =~ s/W$/A/;
759 my $ascii_export_index = $exported_names->{$ascii_name};
761 if (!defined($ascii_export_index))
763 $ascii_export_index = $implementation_names->{$ascii_name};
765 if (!defined($ascii_export_index))
767 if ($opt_verbose > 2)
769 print "Warning: Function '".$comment->{COMMENT_NAME}."' is not an A/W pair.\n";
772 else
774 my $ascii_export = @{$spec_details->{EXPORTS}}[$ascii_export_index];
775 if (@$ascii_export[4] & $FLAG_DOCUMENTED)
777 # Flag these functions as an A/W pair
778 @$ascii_export[4] |= $FLAG_APAIR;
779 @$export[4] |= $FLAG_WPAIR;
783 $found_returns = 1;
785 elsif ( /^Unicode version of ([A-Za-z0-9_]+)\.$/ )
787 @$export[4] |= $FLAG_WPAIR; # Explicitly marked as W version
788 $found_returns = 1;
790 elsif ( /^64\-bit version of ([A-Za-z0-9_]+)\.$/ )
792 @$export[4] |= $FLAG_64PAIR; # Explicitly marked as 64 bit version
793 $found_returns = 1;
795 $found_description_text = 1;
797 else
799 $in_description = 0;
803 if ($found_returns == 0 || $found_description_text == 0)
805 if ($opt_verbose > 2)
807 print "Info: Function '",$comment->{COMMENT_NAME},"' has no ",
808 "description and/or RETURNS section, skipping\n";
810 $spec_details->{NUM_DOCS}--;
811 @$export[4] &= ~$FLAG_DOCUMENTED;
812 return;
816 process_comment_text($comment);
818 # Strip the prototypes return value, call convention, name and brackets
819 # (This leaves it as a list of types and names, or empty for void functions)
820 my $prototype = join(" ", @{$comment->{PROTOTYPE}});
821 $prototype =~ s/ / /g;
823 if ( $prototype =~ /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ )
825 $prototype =~ s/^(.*?) (WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16) (.*?)\( *(.*)/$4/;
826 $comment->{RETURNS} = $1;
828 else
830 $prototype =~ s/^(.*?)([A-Za-z0-9_]+)\( *(.*)/$3/;
831 $comment->{RETURNS} = $1;
834 $prototype =~ s/ *\).*//; # Strip end bracket
835 $prototype =~ s/ *\* */\*/g; # Strip space around pointers
836 $prototype =~ s/ *\, */\,/g; # Strip space around commas
837 $prototype =~ s/^(void|VOID)$//; # If void, leave blank
838 $prototype =~ s/\*([A-Za-z_])/\* $1/g; # Separate pointers from parameter name
839 @{$comment->{PROTOTYPE}} = split ( /,/ ,$prototype);
841 # FIXME: If we have no parameters, make sure we have a PARAMS: None. section
843 # Find header file
844 my $h_file = "";
845 if (@$export[4] & $FLAG_NONAME)
847 $h_file = "Exported by ordinal only. Use GetProcAddress() to obtain a pointer to the function.";
849 else
851 if ($comment->{COMMENT_NAME} ne "")
853 my $tmp = "grep -s -l $comment->{COMMENT_NAME} @opt_header_file_list 2>/dev/null";
854 $tmp = `$tmp`;
855 my $exit_value = $? >> 8;
856 if ($exit_value == 0)
858 $tmp =~ s/\n.*//g;
859 if ($tmp ne "")
861 $h_file = `basename $tmp`;
865 elsif ($comment->{ALT_NAME} ne "")
867 my $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null";
868 $tmp = `$tmp`;
869 my $exit_value = $? >> 8;
870 if ($exit_value == 0)
872 $tmp =~ s/\n.*//g;
873 if ($tmp ne "")
875 $h_file = `basename $tmp`;
879 $h_file =~ s/^ *//;
880 $h_file =~ s/\n//;
881 if ($h_file eq "")
883 $h_file = "Not defined in a Wine header. The function is either undocumented, or missing from Wine."
885 else
887 $h_file = "Defined in \"".$h_file."\".";
891 # Find source file
892 my $c_file = $comment->{FILE};
893 if ($opt_wine_root_dir ne "")
895 my $cfile = $pwd."/".$c_file; # Current dir + file
896 $cfile =~ s/(.+)(\/.*$)/$1/; # Strip the filename
897 $cfile = `cd $cfile && pwd`; # Strip any relative parts (e.g. "../../")
898 $cfile =~ s/\n//; # Strip newline
899 my $newfile = $c_file;
900 $newfile =~ s/(.+)(\/.*$)/$2/; # Strip all but the filename
901 $cfile = $cfile."/".$newfile; # Append filename to base path
902 $cfile =~ s/$opt_wine_root_dir//; # Get rid of the root directory
903 $cfile =~ s/\/\//\//g; # Remove any double slashes
904 $cfile =~ s/^\/+//; # Strip initial directory slash
905 $c_file = $cfile;
907 $c_file = "Implemented in \"".$c_file."\".";
909 # Add the implementation details
910 push (@{$comment->{TEXT}}, "IMPLEMENTATION","",$h_file,"",$c_file);
912 if (@$export[4] & $FLAG_I386)
914 push (@{$comment->{TEXT}}, "", "Available on x86 platforms only.");
916 if (@$export[4] & $FLAG_REGISTER)
918 push (@{$comment->{TEXT}}, "", "This function passes one or more arguments in registers. ",
919 "For more details, please read the source code.");
921 my $source_details = $source_files{$comment->{FILE}}[0];
922 if ($source_details->{DEBUG_CHANNEL} ne "")
924 push (@{$comment->{TEXT}}, "", "Debug channel \"".$source_details->{DEBUG_CHANNEL}."\".");
927 # Write out the documentation for the API
928 output_comment($comment)
931 # process our extra comment and output it if it is suitable.
932 sub process_extra_comment($)
934 my $comment = shift;
936 my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
938 if (!defined($spec_details))
940 if ($opt_verbose > 2)
942 print "Warning: Extra comment '".$comment->{COMMENT_NAME}."' belongs to '".
943 $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
945 return;
948 # Check first to see if this is documentation for the DLL.
949 if ($comment->{COMMENT_NAME} eq $comment->{DLL_NAME})
951 if ($opt_verbose > 2)
953 print "Info: Found DLL documentation\n";
955 for (@{$comment->{TEXT}})
957 push (@{$spec_details->{DESCRIPTION}}, $_);
959 return;
962 # Add the comment to the DLL page as a link
963 push (@{$spec_details->{EXTRA_COMMENTS}},$comment->{COMMENT_NAME});
965 # If we have a prototype, process as a regular comment
966 if (@{$comment->{PROTOTYPE}})
968 $comment->{ORDINAL} = "@";
970 # Add an index for the comment name
971 $spec_details->{EXPORTED_NAMES}{$comment->{COMMENT_NAME}} = $spec_details->{NUM_EXPORTS};
973 # Add a fake exported entry
974 $spec_details->{NUM_EXPORTS}++;
975 my ($ordinal, $call_convention, $exported_name, $implementation_name, $documented) =
976 ("@", "fake", $comment->{COMMENT_NAME}, $comment->{COMMENT_NAME}, 0);
977 my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
978 push (@{$spec_details->{EXPORTS}},[@export]);
979 @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
980 process_comment($comment);
981 return;
984 if ($opt_verbose > 0)
986 print "Processing ",$comment->{COMMENT_NAME},"\n";
989 if (@{$spec_details->{CURRENT_EXTRA}})
991 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
993 if ($opt_verbose > 0)
995 print "Processing old current: ",$current_comment->{COMMENT_NAME},"\n";
997 # Output the current comment
998 process_comment_text($current_comment);
999 output_open_api_file($current_comment->{COMMENT_NAME});
1000 output_api_header($current_comment);
1001 output_api_name($current_comment);
1002 output_api_comment($current_comment);
1003 output_api_footer($current_comment);
1004 output_close_api_file();
1007 if ($opt_verbose > 2)
1009 print "Setting current to ",$comment->{COMMENT_NAME},"\n";
1012 my $comment_copy =
1014 FILE => $comment->{FILE},
1015 COMMENT_NAME => $comment->{COMMENT_NAME},
1016 ALT_NAME => $comment->{ALT_NAME},
1017 DLL_NAME => $comment->{DLL_NAME},
1018 ORDINAL => $comment->{ORDINAL},
1019 RETURNS => $comment->{RETURNS},
1020 PROTOTYPE => [],
1021 TEXT => [],
1024 for (@{$comment->{TEXT}})
1026 push (@{$comment_copy->{TEXT}}, $_);
1028 # Set this comment to be the current extra comment
1029 @{$spec_details->{CURRENT_EXTRA}} = ($comment_copy);
1032 # Write a standardised comment out in the appropriate format
1033 sub output_comment($)
1035 my $comment = shift;
1037 if ($opt_verbose > 0)
1039 print "Processing ",$comment->{COMMENT_NAME},"\n";
1042 if ($opt_verbose > 4)
1044 print "--PROTO--\n";
1045 for (@{$comment->{PROTOTYPE}})
1047 print "'".$_."'\n";
1050 print "--COMMENT--\n";
1051 for (@{$comment->{TEXT} })
1053 print $_."\n";
1057 output_open_api_file($comment->{COMMENT_NAME});
1058 output_api_header($comment);
1059 output_api_name($comment);
1060 output_api_synopsis($comment);
1061 output_api_comment($comment);
1062 output_api_footer($comment);
1063 output_close_api_file();
1066 # Write out an index file for each .spec processed
1067 sub process_index_files()
1069 foreach my $spec_file (keys %spec_files)
1071 my $spec_details = $spec_files{$spec_file}[0];
1072 if (defined ($spec_details->{DLL_NAME}))
1074 if (@{$spec_details->{CURRENT_EXTRA}})
1076 # We have an unwritten extra comment, write it
1077 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
1078 process_extra_comment($current_comment);
1079 @{$spec_details->{CURRENT_EXTRA}} = ();
1081 output_spec($spec_details);
1086 # Write a spec files documentation out in the appropriate format
1087 sub output_spec($)
1089 my $spec_details = shift;
1091 if ($opt_verbose > 2)
1093 print "Writing:",$spec_details->{DLL_NAME},"\n";
1096 # Use the comment output functions for consistency
1097 my $comment =
1099 FILE => $spec_details->{DLL_NAME},
1100 COMMENT_NAME => $spec_details->{DLL_NAME}.".".$spec_details->{DLL_EXT},
1101 ALT_NAME => $spec_details->{DLL_NAME},
1102 DLL_NAME => "",
1103 ORDINAL => "",
1104 RETURNS => "",
1105 PROTOTYPE => [],
1106 TEXT => [],
1108 my $total_implemented = $spec_details->{NUM_FORWARDS} + $spec_details->{NUM_VARS} +
1109 $spec_details->{NUM_FUNCS};
1110 my $percent_implemented = 0;
1111 if ($total_implemented)
1113 $percent_implemented = $total_implemented /
1114 ($total_implemented + $spec_details->{NUM_STUBS}) * 100;
1116 $percent_implemented = int($percent_implemented);
1117 my $percent_documented = 0;
1118 if ($spec_details->{NUM_DOCS})
1120 # Treat forwards and data as documented funcs for statistics
1121 $percent_documented = $spec_details->{NUM_DOCS} / $spec_details->{NUM_FUNCS} * 100;
1122 $percent_documented = int($percent_documented);
1125 # Make a list of the contributors to this DLL. Do this only for the source
1126 # files that make up the DLL, because some directories specify multiple dlls.
1127 my @contributors;
1129 for (@{$spec_details->{SOURCES}})
1131 my $source_details = $source_files{$_}[0];
1132 for (@{$source_details->{CONTRIBUTORS}})
1134 push (@contributors, $_);
1138 my %saw;
1139 @contributors = grep(!$saw{$_}++, @contributors); # remove dups, from perlfaq4 manpage
1140 @contributors = sort @contributors;
1142 # Remove duplicates and blanks
1143 for(my $i=0; $i<@contributors; $i++)
1145 if ($i > 0 && ($contributors[$i] =~ /$contributors[$i-1]/ || $contributors[$i-1] eq ""))
1147 $contributors[$i-1] = $contributors[$i];
1150 undef %saw;
1151 @contributors = grep(!$saw{$_}++, @contributors);
1153 if ($opt_verbose > 3)
1155 print "Contributors:\n";
1156 for (@contributors)
1158 print "'".$_."'\n";
1161 my $contribstring = join (", ", @contributors);
1163 # Create the initial comment text
1164 @{$comment->{TEXT}} = (
1165 "NAME",
1166 $comment->{COMMENT_NAME}
1169 # Add the description, if we have one
1170 if (@{$spec_details->{DESCRIPTION}})
1172 push (@{$comment->{TEXT}}, "DESCRIPTION");
1173 for (@{$spec_details->{DESCRIPTION}})
1175 push (@{$comment->{TEXT}}, $_);
1179 # Add the statistics and contributors
1180 push (@{$comment->{TEXT}},
1181 "STATISTICS",
1182 "Forwards: ".$spec_details->{NUM_FORWARDS},
1183 "Variables: ".$spec_details->{NUM_VARS},
1184 "Stubs: ".$spec_details->{NUM_STUBS},
1185 "Functions: ".$spec_details->{NUM_FUNCS},
1186 "Exports-Total: ".$spec_details->{NUM_EXPORTS},
1187 "Implemented-Total: ".$total_implemented." (".$percent_implemented."%)",
1188 "Documented-Total: ".$spec_details->{NUM_DOCS}." (".$percent_documented."%)",
1189 "CONTRIBUTORS",
1190 "The following people hold copyrights on the source files comprising this dll:",
1192 $contribstring,
1193 "Note: This list may not be complete.",
1194 "For a complete listing, see the Files \"AUTHORS\" and \"Changelog\" in the Wine source tree.",
1198 if ($opt_output_format eq "h")
1200 # Add the exports to the comment text
1201 push (@{$comment->{TEXT}},"EXPORTS");
1202 my $exports = $spec_details->{EXPORTS};
1203 for (@$exports)
1205 my $line = "";
1207 # @$_ => ordinal, call convention, exported name, implementation name, flags;
1208 if (@$_[1] eq "forward")
1210 my $forward_dll = @$_[3];
1211 $forward_dll =~ s/\.(.*)//;
1212 $line = @$_[2]." (forward to ".$1."() in ".$forward_dll."())";
1214 elsif (@$_[1] eq "extern")
1216 $line = @$_[2]." (extern)";
1218 elsif (@$_[1] eq "stub")
1220 $line = @$_[2]." (stub)";
1222 elsif (@$_[1] eq "fake")
1224 # Don't add this function here, it gets listed with the extra documentation
1225 if (!(@$_[4] & $FLAG_WPAIR))
1227 # This function should be indexed
1228 push (@index_entries_list, @$_[3].",".@$_[3]);
1231 elsif (@$_[1] eq "equate" || @$_[1] eq "variable")
1233 $line = @$_[2]." (data)";
1235 else
1237 # A function
1238 if (@$_[4] & $FLAG_DOCUMENTED)
1240 # Documented
1241 $line = @$_[2]." (implemented as ".@$_[3]."())";
1242 if (@$_[2] ne @$_[3])
1244 $line = @$_[2]." (implemented as ".@$_[3]."())";
1246 else
1248 $line = @$_[2]."()";
1250 if (!(@$_[4] & $FLAG_WPAIR))
1252 # This function should be indexed
1253 push (@index_entries_list, @$_[2].",".@$_[3]);
1256 else
1258 $line = @$_[2]." (not documented)";
1261 if ($line ne "")
1263 push (@{$comment->{TEXT}}, $line, "");
1267 # Add links to the extra documentation
1268 if (@{$spec_details->{EXTRA_COMMENTS}})
1270 push (@{$comment->{TEXT}}, "SEE ALSO");
1271 my %htmp;
1272 @{$spec_details->{EXTRA_COMMENTS}} = grep(!$htmp{$_}++, @{$spec_details->{EXTRA_COMMENTS}});
1273 for (@{$spec_details->{EXTRA_COMMENTS}})
1275 push (@{$comment->{TEXT}}, $_."()", "");
1279 # The dll entry should also be indexed
1280 push (@index_entries_list, $spec_details->{DLL_NAME}.",".$spec_details->{DLL_NAME});
1282 # Write out the document
1283 output_open_api_file($spec_details->{DLL_NAME});
1284 output_api_header($comment);
1285 output_api_comment($comment);
1286 output_api_footer($comment);
1287 output_close_api_file();
1289 # Add this dll to the database of dll names
1290 my $output_file = $opt_output_directory."/dlls.db";
1292 # Append the dllname to the output db of names
1293 open(DLLDB,">>$output_file") || die "Couldn't create $output_file\n";
1294 print DLLDB $spec_details->{DLL_NAME},"\n";
1295 close(DLLDB);
1297 if ($opt_output_format eq "s")
1299 output_sgml_dll_file($spec_details);
1300 return;
1305 # OUTPUT FUNCTIONS
1306 # ----------------
1307 # Only these functions know anything about formatting for a specific
1308 # output type. The functions above work only with plain text.
1309 # This is to allow new types of output to be added easily.
1311 # Open the api file
1312 sub output_open_api_file($)
1314 my $output_name = shift;
1315 $output_name = $opt_output_directory."/".$output_name;
1317 if ($opt_output_format eq "h")
1319 $output_name = $output_name.".html";
1321 elsif ($opt_output_format eq "s")
1323 $output_name = $output_name.".sgml";
1325 else
1327 $output_name = $output_name.".".$opt_manual_section;
1329 open(OUTPUT,">$output_name") || die "Couldn't create file '$output_name'\n";
1332 # Close the api file
1333 sub output_close_api_file()
1335 close (OUTPUT);
1338 # Output the api file header
1339 sub output_api_header($)
1341 my $comment = shift;
1343 if ($opt_output_format eq "h")
1345 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1346 print OUTPUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
1347 print OUTPUT "<HTML>\n<HEAD>\n";
1348 print OUTPUT "<LINK REL=\"StyleSheet\" href=\"apidoc.css\" type=\"text/css\">\n";
1349 print OUTPUT "<META NAME=\"GENERATOR\" CONTENT=\"tools/c2man.pl\">\n";
1350 print OUTPUT "<META NAME=\"keywords\" CONTENT=\"Win32,Wine,API,$comment->{COMMENT_NAME}\">\n";
1351 print OUTPUT "<TITLE>Wine API: $comment->{COMMENT_NAME}</TITLE>\n</HEAD>\n<BODY>\n";
1353 elsif ($opt_output_format eq "s")
1355 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n",
1356 "<sect1>\n",
1357 "<title>$comment->{COMMENT_NAME}</title>\n";
1359 else
1361 print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n".
1362 ".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"".
1363 "Wine API\" \"Wine API\"\n";
1367 sub output_api_footer($)
1369 if ($opt_output_format eq "h")
1371 print OUTPUT "<hr><p><i class=\"copy\">Copyright &copy ".$year." The Wine Project.".
1372 " All trademarks are the property of their respective owners.".
1373 " Visit <a href=\"http://www.winehq.org\">WineHQ</a> for license details.".
1374 " Generated $date.</i></p>\n</body>\n</html>\n";
1376 elsif ($opt_output_format eq "s")
1378 print OUTPUT "</sect1>\n";
1379 return;
1381 else
1386 sub output_api_section_start($$)
1388 my $comment = shift;
1389 my $section_name = shift;
1391 if ($opt_output_format eq "h")
1393 print OUTPUT "\n<h2 class=\"section\">",$section_name,"</h2>\n";
1395 elsif ($opt_output_format eq "s")
1397 print OUTPUT "<bridgehead>",$section_name,"</bridgehead>\n";
1399 else
1401 print OUTPUT "\n\.SH ",$section_name,"\n";
1405 sub output_api_section_end()
1407 # Not currently required by any output formats
1410 sub output_api_name($)
1412 my $comment = shift;
1413 my $readable_name = $comment->{COMMENT_NAME};
1414 $readable_name =~ s/-/ /g; # make section names more readable
1416 output_api_section_start($comment,"NAME");
1419 my $dll_ordinal = "";
1420 if ($comment->{ORDINAL} ne "")
1422 $dll_ordinal = "(".$comment->{DLL_NAME}.".".$comment->{ORDINAL}.")";
1424 if ($opt_output_format eq "h")
1426 print OUTPUT "<p><b class=\"func_name\">",$readable_name,
1427 "</b>&nbsp;&nbsp;<i class=\"dll_ord\">",
1428 ,$dll_ordinal,"</i></p>\n";
1430 elsif ($opt_output_format eq "s")
1432 print OUTPUT "<para>\n <command>",$readable_name,"</command> <emphasis>",
1433 $dll_ordinal,"</emphasis>\n</para>\n";
1435 else
1437 print OUTPUT "\\fB",$readable_name,"\\fR ",$dll_ordinal;
1440 output_api_section_end();
1443 sub output_api_synopsis($)
1445 my $comment = shift;
1446 my @fmt;
1448 output_api_section_start($comment,"SYNOPSIS");
1450 if ($opt_output_format eq "h")
1452 print OUTPUT "<pre class=\"proto\">\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1453 @fmt = ("", "\n", "<tt class=\"param\">", "</tt>");
1455 elsif ($opt_output_format eq "s")
1457 print OUTPUT "<screen>\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1458 @fmt = ("", "\n", "<emphasis>", "</emphasis>");
1460 else
1462 print OUTPUT $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1463 @fmt = ("", "\n", "\\fI", "\\fR");
1466 # Since our prototype is output in a pre-formatted block, line up the
1467 # parameters and parameter comments in the same column.
1469 # First caluculate where the columns should start
1470 my $biggest_length = 0;
1471 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1473 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1474 if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1476 my $length = length $1;
1477 if ($length > $biggest_length)
1479 $biggest_length = $length;
1484 # Now pad the string with blanks
1485 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1487 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1488 if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1490 my $pad_len = $biggest_length - length $1;
1491 my $padding = " " x ($pad_len);
1492 ${@{$comment->{PROTOTYPE}}}[$i] = $1.$padding.$2;
1496 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1498 # Format the parameter name
1499 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1500 my $comma = ($i == @{$comment->{PROTOTYPE}}-1) ? "" : ",";
1501 $line =~ s/(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ $fmt[0]$1$fmt[2]$2$fmt[3]$comma$fmt[1]/;
1502 print OUTPUT $line;
1505 if ($opt_output_format eq "h")
1507 print OUTPUT " )\n</pre>\n";
1509 elsif ($opt_output_format eq "s")
1511 print OUTPUT " )\n</screen>\n";
1513 else
1515 print OUTPUT " )\n";
1518 output_api_section_end();
1521 sub output_api_comment($)
1523 my $comment = shift;
1524 my $open_paragraph = 0;
1525 my $open_raw = 0;
1526 my $param_docs = 0;
1527 my @fmt;
1529 if ($opt_output_format eq "h")
1531 @fmt = ("<p>", "</p>\n", "<tt class=\"const\">", "</tt>", "<b class=\"emp\">", "</b>",
1532 "<tt class=\"coderef\">", "</tt>", "<tt class=\"param\">", "</tt>",
1533 "<i class=\"in_out\">", "</i>", "<pre class=\"raw\">\n", "</pre>\n",
1534 "<table class=\"tab\"><colgroup><col><col><col></colgroup><tbody>\n",
1535 "</tbody></table>\n","<tr><td>","</td></tr>\n","</td>","</td><td>");
1537 elsif ($opt_output_format eq "s")
1539 @fmt = ("<para>\n","\n</para>\n","<constant>","</constant>","<emphasis>","</emphasis>",
1540 "<command>","</command>","<constant>","</constant>","<emphasis>","</emphasis>",
1541 "<screen>\n","</screen>\n",
1542 "<informaltable frame=\"none\">\n<tgroup cols=\"3\">\n<tbody>\n",
1543 "</tbody>\n</tgroup>\n</informaltable>\n","<row><entry>","</entry></row>\n",
1544 "</entry>","</entry><entry>");
1546 else
1548 @fmt = ("\.PP\n", "\n", "\\fB", "\\fR", "\\fB", "\\fR", "\\fB", "\\fR", "\\fI", "\\fR",
1549 "\\fB", "\\fR ", "", "", "", "","","\n.PP\n","","");
1552 # Extract the parameter names
1553 my @parameter_names;
1554 for (@{$comment->{PROTOTYPE}})
1556 if ( /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ )
1558 push (@parameter_names, $2);
1562 for (@{$comment->{TEXT}})
1564 if ($opt_output_format eq "h" || $opt_output_format eq "s")
1566 # Map special characters
1567 s/\&/\&amp;/g;
1568 s/\</\&lt;/g;
1569 s/\>/\&gt;/g;
1570 s/\([Cc]\)/\&copy;/g;
1571 s/\(tm\)/&#174;/;
1574 if ( s/^\|// )
1576 # Raw output
1577 if ($open_raw == 0)
1579 if ($open_paragraph == 1)
1581 # Close the open paragraph
1582 print OUTPUT $fmt[1];
1583 $open_paragraph = 0;
1585 # Start raw output
1586 print OUTPUT $fmt[12];
1587 $open_raw = 1;
1589 if ($opt_output_format eq "")
1591 print OUTPUT ".br\n"; # Prevent 'man' running these lines together
1593 print OUTPUT $_,"\n";
1595 else
1597 if ($opt_output_format eq "h")
1599 # Link to the file in WineHQ cvs
1600 s/^(Implemented in \")(.+?)(\"\.)/$1$2$3 http:\/\/source.winehq.org\/source\/$2/g;
1602 # Highlight strings
1603 s/(\".+?\")/$fmt[2]$1$fmt[3]/g;
1604 # Highlight literal chars
1605 s/(\'.\')/$fmt[2]$1$fmt[3]/g;
1606 s/(\'.{2}\')/$fmt[2]$1$fmt[3]/g;
1607 # Highlight numeric constants
1608 s/( |\-|\+|\.|\()([0-9\-\.]+)( |\-|$|\.|\,|\*|\?|\))/$1$fmt[2]$2$fmt[3]$3/g;
1610 # Leading cases ("xxxx:","-") start new paragraphs & are emphasised
1611 # FIXME: Using bullet points for leading '-' would look nicer.
1612 if ($open_paragraph == 1)
1614 s/^(\-)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1615 s/^([[A-Za-z\-]+\:)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1617 else
1619 s/^(\-)/$fmt[4]$1$fmt[5]/;
1620 s/^([[A-Za-z\-]+\:)/$fmt[4]$1$fmt[5]/;
1623 if ($opt_output_format eq "h")
1625 # Html uses links for API calls
1626 while ( /([A-Za-z_]+[A-Za-z_0-9-]+)(\(\))/)
1628 my $link = $1;
1629 my $readable_link = $1;
1630 $readable_link =~ s/-/ /g;
1632 s/([A-Za-z_]+[A-Za-z_0-9-]+)(\(\))/<a href\=\"$link\.html\">$readable_link<\/a>/;
1634 # Index references
1635 s/\{\{(.*?)\}\}\{\{(.*?)\}\}/<a href\=\"$2\.html\">$1<\/a>/g;
1636 s/ ([A-Z_])(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1637 # And references to COM objects (hey, they'll get documented one day)
1638 s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g;
1639 # Convert any web addresses to real links
1640 s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g;
1642 else
1644 if ($opt_output_format eq "")
1646 # Give the man section for API calls
1647 s/ ([A-Za-z_]+[A-Za-z_0-9-]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g;
1649 else
1651 # Highlight API calls
1652 s/ ([A-Za-z_]+[A-Za-z_0-9-]+\(\))/ $fmt[6]$1$fmt[7]/g;
1655 # And references to COM objects
1656 s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g;
1659 if ($open_raw == 1)
1661 # Finish the raw output
1662 print OUTPUT $fmt[13];
1663 $open_raw = 0;
1666 if ( /^[A-Z]+$/ || /^SEE ALSO$/ )
1668 # Start of a new section
1669 if ($open_paragraph == 1)
1671 if ($param_docs == 1)
1673 print OUTPUT $fmt[17],$fmt[15];
1675 else
1677 print OUTPUT $fmt[1];
1679 $open_paragraph = 0;
1681 output_api_section_start($comment,$_);
1682 if ( /^PARAMS$/ || /^MEMBERS$/ )
1684 print OUTPUT $fmt[14];
1685 $param_docs = 1;
1687 else
1689 #print OUTPUT $fmt[15];
1690 $param_docs = 0;
1693 elsif ( /^$/ )
1695 # Empty line, indicating a new paragraph
1696 if ($open_paragraph == 1)
1698 if ($param_docs == 0)
1700 print OUTPUT $fmt[1];
1701 $open_paragraph = 0;
1705 else
1707 if ($param_docs == 1)
1709 if ($open_paragraph == 1)
1711 # For parameter docs, put each parameter into a new paragraph/table row
1712 print OUTPUT $fmt[17];
1713 $open_paragraph = 0;
1715 s/(\[.+\])( *)/$fmt[19]$fmt[10]$1$fmt[11]$fmt[19] /; # Format In/Out
1717 else
1719 # Within paragraph lines, prevent lines running together
1720 $_ = $_." ";
1723 # Format parameter names where they appear in the comment
1724 for my $parameter_name (@parameter_names)
1726 s/(^|[ \.\,\(\-\*])($parameter_name)($|[ \.\)\,\-\/]|(\=[^"]))/$1$fmt[8]$2$fmt[9]$3/g;
1728 # Structure dereferences include the dereferenced member
1729 s/(\-\>[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1730 s/(\-\&gt\;[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1732 if ($open_paragraph == 0)
1734 if ($param_docs == 1)
1736 print OUTPUT $fmt[16];
1738 else
1740 print OUTPUT $fmt[0];
1742 $open_paragraph = 1;
1744 # Anything in all uppercase on its own gets emphasised
1745 s/(^|[ \.\,\(\[\|\=])([A-Z]+?[A-Z0-9_]+)($|[ \.\,\*\?\|\)\=\'])/$1$fmt[6]$2$fmt[7]$3/g;
1747 print OUTPUT $_;
1751 if ($open_raw == 1)
1753 print OUTPUT $fmt[13];
1755 if ($open_paragraph == 1)
1757 print OUTPUT $fmt[1];
1761 # Create the master index file
1762 sub output_master_index_files()
1764 if ($opt_output_format eq "")
1766 return; # No master index for man pages
1769 if ($opt_output_format eq "h")
1771 # Append the index entries to the output db of index entries
1772 my $output_file = $opt_output_directory."/index.db";
1773 open(INDEXDB,">>$output_file") || die "Couldn't create $output_file\n";
1774 for (@index_entries_list)
1776 $_ =~ s/A\,/\,/;
1777 print INDEXDB $_."\n";
1779 close(INDEXDB);
1782 # Use the comment output functions for consistency
1783 my $comment =
1785 FILE => "",
1786 COMMENT_NAME => "The Wine Api Guide",
1787 ALT_NAME => "The Wine Api Guide",
1788 DLL_NAME => "",
1789 ORDINAL => "",
1790 RETURNS => "",
1791 PROTOTYPE => [],
1792 TEXT => [],
1795 if ($opt_output_format eq "s")
1797 $comment->{COMMENT_NAME} = "Introduction";
1798 $comment->{ALT_NAME} = "Introduction",
1800 elsif ($opt_output_format eq "h")
1802 @{$comment->{TEXT}} = (
1803 "NAME",
1804 $comment->{COMMENT_NAME},
1805 "INTRODUCTION",
1809 # Create the initial comment text
1810 push (@{$comment->{TEXT}},
1811 "This document describes the Api calls made available",
1812 "by Wine. They are grouped by the dll that exports them.",
1814 "Please do not edit this document, since it is generated automatically",
1815 "from the Wine source code tree. Details on updating this documentation",
1816 "are given in the \"Wine Developers Guide\".",
1817 "CONTRIBUTORS",
1818 "Api documentation is generally written by the person who ",
1819 "implements a given Api call. Authors of each dll are listed in the overview ",
1820 "section for that dll. Additional contributors who have updated source files ",
1821 "but have not entered their names in a copyright statement are noted by an ",
1822 "entry in the file \"Changelog\" from the Wine source code distribution.",
1826 # Read in all dlls from the database of dll names
1827 my $input_file = $opt_output_directory."/dlls.db";
1828 my @dlls = `cat $input_file|sort|uniq`;
1830 if ($opt_output_format eq "h")
1832 # HTML gets a list of all the dlls and an index. For docbook the index creates this for us
1833 push (@{$comment->{TEXT}},
1834 "INDEX",
1835 "For an alphabetical listing of the functions available, please click the ",
1836 "first letter of the functions name below:","",
1837 "[ _(), A(), B(), C(), D(), E(), F(), G(), H(), ".
1838 "I(), J(), K(), L(), M(), N(), O(), P(), Q(), ".
1839 "R(), S(), T(), U(), V(), W(), X(), Y(), Z() ]", "",
1840 "DLLS",
1841 "Each dll provided by Wine is documented individually. The following dlls are provided :",
1844 # Add the dlls to the comment
1845 for (@dlls)
1847 $_ =~ s/(\..*)?\n/\(\)/;
1848 push (@{$comment->{TEXT}}, $_, "");
1850 output_open_api_file("index");
1852 elsif ($opt_output_format eq "s")
1854 # Just write this as the initial blurb, with a chapter heading
1855 output_open_api_file("blurb");
1856 print OUTPUT "<chapter id =\"blurb\">\n<title>Introduction to The Wine Api Guide</title>\n"
1859 # Write out the document
1860 output_api_header($comment);
1861 output_api_comment($comment);
1862 output_api_footer($comment);
1863 if ($opt_output_format eq "s")
1865 print OUTPUT "</chapter>\n" # finish the chapter
1867 output_close_api_file();
1869 if ($opt_output_format eq "s")
1871 output_sgml_master_file(\@dlls);
1872 return;
1874 if ($opt_output_format eq "h")
1876 output_html_index_files();
1877 output_html_stylesheet();
1878 return;
1882 # Write the master wine-api.sgml, linking it to each dll.
1883 sub output_sgml_master_file($)
1885 my $dlls = shift;
1887 output_open_api_file("wine-api");
1888 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1889 print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n";
1890 print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n";
1892 # List the entities
1893 for (@$dlls)
1895 $_ =~ s/(\..*)?\n//;
1896 print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n"
1899 print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine Api Guide</title></bookinfo>\n\n";
1900 print OUTPUT " &blurb;\n";
1902 for (@$dlls)
1904 print OUTPUT " &",$_,";\n"
1906 print OUTPUT "\n\n</book>\n";
1908 output_close_api_file();
1911 # Produce the sgml for the dll chapter from the generated files
1912 sub output_sgml_dll_file($)
1914 my $spec_details = shift;
1916 # Make a list of all the documentation files to include
1917 my $exports = $spec_details->{EXPORTS};
1918 my @source_files = ();
1919 for (@$exports)
1921 # @$_ => ordinal, call convention, exported name, implementation name, documented;
1922 if (@$_[1] ne "forward" && @$_[1] ne "extern" && @$_[1] ne "stub" && @$_[1] ne "equate" &&
1923 @$_[1] ne "variable" && @$_[1] ne "fake" && @$_[4] & 1)
1925 # A documented function
1926 push (@source_files,@$_[3]);
1930 push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
1932 @source_files = sort @source_files;
1934 # create a new chapter for this dll
1935 my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
1936 open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
1937 print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
1938 output_close_api_file();
1940 # Add the sorted documentation, cleaning up as we go
1941 `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`;
1942 for (@source_files)
1944 `cat $opt_output_directory/$_.sgml >>$tmp_name`;
1945 `rm -f $opt_output_directory/$_.sgml`;
1948 # close the chapter, and overwite the dll source
1949 open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
1950 print OUTPUT "</chapter>\n";
1951 close OUTPUT;
1952 `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`;
1955 # Write the html index files containing the function names
1956 sub output_html_index_files()
1958 if ($opt_output_format ne "h")
1960 return;
1963 my @letters = ('_', 'A' .. 'Z');
1965 # Read in all functions
1966 my $input_file = $opt_output_directory."/index.db";
1967 my @funcs = `cat $input_file|sort|uniq`;
1969 for (@letters)
1971 my $letter = $_;
1972 my $comment =
1974 FILE => "",
1975 COMMENT_NAME => "",
1976 ALT_NAME => "",
1977 DLL_NAME => "",
1978 ORDINAL => "",
1979 RETURNS => "",
1980 PROTOTYPE => [],
1981 TEXT => [],
1984 $comment->{COMMENT_NAME} = $letter." Functions";
1985 $comment->{ALT_NAME} = $letter." Functions";
1987 push (@{$comment->{TEXT}},
1988 "NAME",
1989 $comment->{COMMENT_NAME},
1990 "FUNCTIONS"
1993 # Add the functions to the comment
1994 for (@funcs)
1996 my $first_char = substr ($_, 0, 1);
1997 $first_char = uc $first_char;
1999 if ($first_char eq $letter)
2001 my $name = $_;
2002 my $file;
2003 $name =~ s/(^.*?)\,(.*?)\n/$1/;
2004 $file = $2;
2005 push (@{$comment->{TEXT}}, "{{".$name."}}{{".$file."}}","");
2009 # Write out the document
2010 output_open_api_file($letter);
2011 output_api_header($comment);
2012 output_api_comment($comment);
2013 output_api_footer($comment);
2014 output_close_api_file();
2018 # Output the stylesheet for HTML output
2019 sub output_html_stylesheet()
2021 if ($opt_output_format ne "h")
2023 return;
2026 my $css;
2027 ($css = <<HERE_TARGET) =~ s/^\s+//gm;
2029 * Default styles for Wine HTML Documentation.
2031 * This style sheet should be altered to suit your needs/taste.
2033 BODY { /* Page body */
2034 background-color: white;
2035 color: black;
2036 font-family: Tahoma,sans-serif;
2037 font-style: normal;
2038 font-size: 10pt;
2040 a:link { color: #4444ff; } /* Links */
2041 a:visited { color: #333377 }
2042 a:active { color: #0000dd }
2043 H2.section { /* Section Headers */
2044 font-family: sans-serif;
2045 color: #777777;
2046 background-color: #F0F0FE;
2047 margin-left: 0.2in;
2048 margin-right: 1.0in;
2050 b.func_name { /* Function Name */
2051 font-size: 10pt;
2052 font-style: bold;
2054 i.dll_ord { /* Italicised DLL+ordinal */
2055 color: #888888;
2056 font-family: sans-serif;
2057 font-size: 8pt;
2059 p { /* Paragraphs */
2060 margin-left: 0.5in;
2061 margin-right: 0.5in;
2063 table { /* tables */
2064 margin-left: 0.5in;
2065 margin-right: 0.5in;
2067 pre.proto /* API Function prototype */
2069 border-style: solid;
2070 border-width: 1px;
2071 border-color: #777777;
2072 background-color: #F0F0BB;
2073 color: black;
2074 font-size: 10pt;
2075 vertical-align: top;
2076 margin-left: 0.5in;
2077 margin-right: 1.0in;
2079 pre.raw { /* Raw text output */
2080 margin-left: 0.6in;
2081 margin-right: 1.1in;
2082 background-color: #8080DC;
2084 tt.param { /* Parameter name */
2085 font-style: italic;
2086 color: blue;
2088 tt.const { /* Constant */
2089 color: red;
2091 i.in_out { /* In/Out */
2092 font-size: 8pt;
2093 color: grey;
2095 tt.coderef { /* Code in description text */
2096 color: darkgreen;
2098 b.emp /* Emphasis */ {
2099 font-style: bold;
2100 color: darkblue;
2102 i.footer { /* Footer */
2103 font-family: sans-serif;
2104 font-size: 6pt;
2105 color: darkgrey;
2107 HERE_TARGET
2109 my $output_file = "$opt_output_directory/apidoc.css";
2110 open(CSS,">$output_file") || die "Couldn't create the file $output_file\n";
2111 print CSS $css;
2112 close(CSS);
2116 sub usage()
2118 print "\nCreate API Documentation from Wine source code.\n\n",
2119 "Usage: c2man.pl [options] {-w <spec>} {-I <include>} {<source>}\n",
2120 "Where: <spec> is a .spec file giving a DLL's exports.\n",
2121 " <include> is an include directory used by the DLL.\n",
2122 " <source> is a source file of the DLL.\n",
2123 " The above can be given multiple times on the command line, as appropriate.\n",
2124 "Options:\n",
2125 " -Th : Output HTML instead of a man page\n",
2126 " -Ts : Output SGML (Docbook source) instead of a man page\n",
2127 " -C <dir> : Source directory, to find source files if they are not found in the\n",
2128 " current directory. Default is \"",$opt_source_dir,"\"\n",
2129 " -R <dir> : Root of build directory, default is \"",$opt_wine_root_dir,"\"\n",
2130 " -o <dir> : Create output in <dir>, default is \"",$opt_output_directory,"\"\n",
2131 " -s <sect>: Set manual section to <sect>, default is ",$opt_manual_section,"\n",
2132 " -e : Output \"FIXME\" documentation from empty comments.\n",
2133 " -v : Verbosity. Can be given more than once for more detail.\n";
2138 # Main
2141 # Print usage if we're called with no args
2142 if( @ARGV == 0)
2144 usage();
2147 # Process command line options
2148 while(defined($_ = shift @ARGV))
2150 if( s/^-// )
2152 # An option.
2153 for ($_)
2155 /^o$/ && do { $opt_output_directory = shift @ARGV; last; };
2156 s/^S// && do { $opt_manual_section = $_; last; };
2157 /^Th$/ && do { $opt_output_format = "h"; last; };
2158 /^Ts$/ && do { $opt_output_format = "s"; last; };
2159 /^v$/ && do { $opt_verbose++; last; };
2160 /^e$/ && do { $opt_output_empty = 1; last; };
2161 /^L$/ && do { last; };
2162 /^w$/ && do { @opt_spec_file_list = (@opt_spec_file_list, shift @ARGV); last; };
2163 s/^I// && do { if ($_ ne ".") {
2164 my $include = $_."/*.h";
2165 $include =~ s/\/\//\//g;
2166 my $have_headers = `ls $include >/dev/null 2>&1`;
2167 if ($? >> 8 == 0) { @opt_header_file_list = (@opt_header_file_list, $include); }
2169 last;
2171 s/^C// && do {
2172 if ($_ ne "") { $opt_source_dir = $_; }
2173 last;
2175 s/^R// && do { if ($_ =~ /^\//) { $opt_wine_root_dir = $_; }
2176 else { $opt_wine_root_dir = `cd $pwd/$_ && pwd`; }
2177 $opt_wine_root_dir =~ s/\n//;
2178 $opt_wine_root_dir =~ s/\/\//\//g;
2179 if (! $opt_wine_root_dir =~ /\/$/ ) { $opt_wine_root_dir = $opt_wine_root_dir."/"; };
2180 last;
2182 die "Unrecognised option $_\n";
2185 else
2187 # A source file.
2188 push (@opt_source_file_list, $_);
2192 # Remove duplicate include directories
2193 my %htmp;
2194 @opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list);
2196 if ($opt_verbose > 3)
2198 print "Output dir:'".$opt_output_directory."'\n";
2199 print "Section :'".$opt_manual_section."'\n";
2200 print "Format :'".$opt_output_format."'\n";
2201 print "Source dir:'".$opt_source_dir."'\n";
2202 print "Root :'".$opt_wine_root_dir."'\n";
2203 print "Spec files:'@opt_spec_file_list'\n";
2204 print "Includes :'@opt_header_file_list'\n";
2205 print "Sources :'@opt_source_file_list'\n";
2208 if (@opt_spec_file_list == 0)
2210 exit 0; # Don't bother processing non-dll files
2213 # Make sure the output directory exists
2214 unless (-d $opt_output_directory)
2216 mkdir $opt_output_directory or die "Cannot create directory $opt_output_directory\n";
2219 # Read in each .spec files exports and other details
2220 while(my $spec_file = shift @opt_spec_file_list)
2222 process_spec_file($spec_file);
2225 if ($opt_verbose > 3)
2227 foreach my $spec_file ( keys %spec_files )
2229 print "in '$spec_file':\n";
2230 my $spec_details = $spec_files{$spec_file}[0];
2231 my $exports = $spec_details->{EXPORTS};
2232 for (@$exports)
2234 print @$_[0].",".@$_[1].",".@$_[2].",".@$_[3]."\n";
2239 # Extract and output the comments from each source file
2240 while(defined($_ = shift @opt_source_file_list))
2242 process_source_file($_);
2245 # Write the index files for each spec
2246 process_index_files();
2248 # Write the master index file
2249 output_master_index_files();
2251 exit 0;