2 # $Id: scwmdoc.in,v 1.1 2000-05-19 06:51:06 sgt Exp $ -*- perl -*-
4 # Copyright (C) 1998-1999 Greg J. Badros
6 # scwmdoc pulls out the SCWM_PROC declarations and comments
7 # from a SCWM .c source file and processes them to create
10 # Usage: perl scwmdoc <filenames-to-extract-from>
17 # Extract documentation from comments in C source files
18 # and generate a plaintext listing of the procedures, and
19 # DocBook SGML output to create parts of the Scwm manual.
21 # Note that this script does lots of important error checking, and
22 # produces error and warning messages that look like grep, so emacs'
23 # compile-mode (and probably grep-mode) can be used to find problems
24 # with the documentation using M-x compile (C-x c) scwmdoc *.c >
25 # /dev/null then M-x next-error (C-x `)
29 # ./utilities/dev/scwmdoc -o doc/scwm scwm/*.c > doc/scwm-procedures.txt
30 # or from Emacs, for warnings only
31 # ./utilities/dev/scwmdoc scwm/*.c > /dev/null
33 # See also extract.scm, an implementation in guile-scheme instead of perl
34 # written by Harvey Stein
35 # Main differences: that code is ~7x slower
36 # perhaps not as well tested
37 # more and better abstractions in the scheme version
38 # It uses guile (easier acceptance by guile-devs, perhaps)
39 # and it's pretty out of date now --03/24/99 gjb
41 # TODO: make concept section id-s use a prefix (e.g., shadow-factor user-option
42 # variable's doc id conflicts with the shadow-factor getter primitive)
44 require 5.004; # uses "for my $var"
46 use constant TRUE => (1==1);
47 use constant FALSE => (1==0);
50 my $getopts_option_letters = 'hqQDo:sCd:V:O:H:N:nP:SF';
51 use vars qw($opt_h $opt_q $opt_Q $opt_D $opt_o $opt_s $opt_C $opt_F
52 $opt_d $opt_V $opt_O $opt_H $opt_N $opt_n $opt_P $opt_S);
54 my $scwm_version = "pre-1.0";
56 sub script_usage ( ) {
57 print "@_\nUsage: $0 [-$getopts_option_letters]
58 -q Be reasonably quiet-- do not warn about spacing or purpose strings
59 -Q Be completely QUIET-- no warnings (still prints errors)
60 -D Debugging output on
61 -V file Output user-options scheme module to file [obsoleted] -- skip if not given
62 -O file Output user-options documentation to file -- skip if not given
63 -H file Output user hooks documentation to file -- skip if not given
64 -N file Output concepts documentation to file -- skip if not given
65 -o file Send sgml output to file -- no sgml output unless this is given
66 -s Run ispell on the comments and reports warnings for its responses
67 -C Only output concepts chapters
68 -n No warning for problems with comments
69 -d URL Set scwmdir source code base to path (e.g., file:///usr/local/src/scwm)
70 -P prefix Set package prefix (e.g., -PCL to use CL_* instead of SCWM_*)
71 -S Skip the procedures chapters-- makes it much faster to convert to HTML
72 -F Skip all procedures except those starting with \"F\" for fast check on output
79 my $pkg_name = basename($ENV{PWD});
82 my $fReallyQuiet = FALSE;
83 my $fWarnIfNoComment = TRUE;
85 # Text from sgml files making up the book
87 my $ref_concepts_intro_sgml;
88 my $ref_hooks_intro_sgml;
89 my $ref_vars_intro_sgml;
90 my $ref_proc_bygroup_intro_sgml;
92 my $fAddedStyleHeader = FALSE;
95 getopts($getopts_option_letters);
97 script_usage() if ($opt_h);
99 #print STDERR "opt_D = $opt_D, ", TRUE, "\n";
101 $fDebug = TRUE if $opt_D;
102 $fQuiet = TRUE if $opt_q || $opt_Q;
103 $fReallyQuiet = TRUE if $opt_Q;
104 $fWarnIfNoComment = FALSE if $opt_n;
105 $prefix = $opt_P if $opt_P;
107 # link directly to the cvsweb checkout of the file;
108 # I could even make this go to the version that the docs were generated
109 # from, but I think it's better to look at the latest version for now
110 # (at least until 1.0)
111 my $scwmdir = $opt_d || "http://scwm.mit.edu/cgi-bin/cvsweb.new/~checkout~";
113 # maps from a procedure name to a hash
114 # containing "usage", "purpose", "comment", "markup", "file", "line"
117 # maps from a filename to a list reference containing the names of
118 # primitives defined in that file
121 # Maps from concepts/hooks/vars to a hash containing
122 # "comment", "markup", "file", "line"
127 # list of the argument names in the current C function
130 # Current scheme environment
131 # i.e., the last define-module seen, reset at new files
132 my $current_module = "";
134 # The last public scheme variable definition
135 # Useful for ";;;**VAR" w/o explicit name in .scm files
136 my $last_public_scheme_definition = "";
141 # dictionary of scwm-specific words, in the same directory as this script
142 (my $dictionary = $0)=~s#[^/]*$#dictionary#;
144 my $pid = open2( \*ISPELL_RESPONSE,
145 \*ISPELL, "ispell -a -p $dictionary") or die "Could not open \'ispell -a\' pipe: $!";
152 $current_module = "";
155 my $fScheme = ($ARGV =~ /.scm$/);
156 if (m/^${prefix}_[IW]?PROC/o) {
158 my $filename = $ARGV;
160 while (defined($_ = <>) && $_ !~ m/^\s*\{/) {
163 $current_module = "";
169 ProcessHeader($filename, $line, $header);
170 } elsif (m%/\*\*?\s*SCWM[_-]?VALIDATE\s*:\s*(.*)\s*\*/%) {
172 $argslist =~ s/\n/ /g;
173 $argslist =~ s/[ \t]+//g;
174 @argnames = split(/,/, $argslist); # this array is for comparison w/ C code
175 } elsif (m/^\s*scm_wrong_type_arg\s*\(\s*FUNC_NAME\s*,\s*(\d+)\s*,\s*(\w+)\s*\)/) {
176 my $argnum = $1; # 1-based
178 check_arg_name_number_match("scm_wrong_type_arg",$1,$2,\@argnames);
179 } elsif (m/^\s*SCWM_WRONG_TYPE_ARG\s*\(\s*(\d+)\s*,\s*(\w+)\s*\)/) {
180 my $argnum = $1; # 1-based
182 check_arg_name_number_match("SCWM_WRONG_TYPE_ARG",$1,$2,\@argnames);
183 } elsif (m/^\s*(VALIDATE_[^\(]*)\s*\(\s*(\d+)\s*,\s*(\w+)\s*/) {
184 my $validate_fn = $1;
185 my $argnum = $2; # 1-based
187 check_arg_name_number_match($1,$2,$3,\@argnames);
188 # } elsif (m/${prefix}_HOOK\s*\(\s*([^,]+)\s*,\s*\"([^\"]+)\"\s*,\s*(\d+)\s*,\s*(\".*?[^\\]\")\s*\)\s*;/o) {
189 } elsif (m/${prefix}_HOOK\s*\(\s*([^,]+)\s*,\s*\"([^\"]+)\"\s*,\s*(\d+)\s*,\s*$/o) {
191 my $scheme_hookname = $2;
193 my $filename = $ARGV;
196 # print STDERR "Got hook body = $body\n";
197 if (!CNameMatchesSchemeName($c_hookname,$scheme_hookname) &&
198 ($c_hookname !~ m/^x_\S+_hook/ ||
199 !CNameMatchesSchemeNameIgnoreCase($c_hookname,$scheme_hookname))) {
200 # We permit the X-foo-hook's to mismatch as long as only the case is different
201 print STDERR "$ARGV:$.:**** Scheme hook name `$scheme_hookname' does not match `$c_hookname'\n";
203 if ($body !~ m%^\s*\"(.*)$%) {
205 print STDERR "$ARGV:$.:**** Hook $scheme_hookname is missing docstring\n";
208 # $body = ReadRestOfComment($1);
209 if (!($body =~ s%\"\s*\)\s*;\s*$%%m)) {
210 $body = ReadRestOfDocstring($filename,$line,"$1\n");
212 # Handle single-line docstrings
216 print STDERR "GOT hook $c_hookname,$scheme_hookname at ",
217 " $filename:$line body:\n$body\n";
219 ProcessHookComment($filename, $line, $scheme_hookname, $num_args, $body);
220 } elsif (m/${prefix}_VAR(?:_READ_ONLY|_INIT(?:_PERMANENT)?)\s*\(\s*([^,]+)\s*,\s*\"([^"]+)"/o) {
221 #FIXGJB: not ideal, since macro needs to end on same line
222 # or else the comment will not get found --02/11/99 gjb
224 my $scheme_varname = $2;
225 my $filename = $ARGV;
228 # print STDERR "Found var $c_varname\n";
229 if ($c_varname ne "NULL" &&
230 !CNameMatchesSchemeName($c_varname,$scheme_varname)) {
231 print STDERR "$ARGV:$.:**** Scheme variable name `$scheme_varname' does not match `$c_varname'\n";
233 if ($body !~ m%/\*\*\s*(.*)%) {
235 print STDERR "$ARGV:$.:**** Variable $scheme_varname is missing /** description comment\n";
238 $body = ReadRestOfComment($1);
240 print STDERR "GOT variable $c_varname,$scheme_varname at ",
241 " $filename:$line body:\n$body\n";
243 ProcessVarComment($filename, $line, $scheme_varname, $body);
244 } elsif ($fScheme && m%^\(define-module\s+(\([^\)]*\))%) {
245 $current_module = $1;
246 } elsif ($fScheme && m%^\(define\*?-public\s+\(([^\s\)]+)%) {
247 my $scheme_header = $_;
249 my $filename = $ARGV;
251 # Keep reading lines until eof or a paren in leftmost column
252 while (defined($_ = <>) && $_ !~ m/^\(/) {
253 $scheme_header .= $_;
256 $current_module = "";
259 ProcessSchemeHeader($filename, $current_module, $proc_name, $line, $scheme_header);
260 redo if (defined($_)); # start loop over w/o reading next line
261 } elsif ($fScheme && m%^\(define-string-matcher\s+(\S+)%) {
262 my $scheme_header = $_;
264 my $filename = $ARGV;
266 # Keep reading lines until eof or a paren in leftmost column
267 while (defined($_ = <>) && $_ !~ m/^\(/) {
268 $scheme_header .= $_;
271 $current_module = "";
274 ProcessSchemeExtension($filename, $current_module, $proc_name, $line, $scheme_header);
275 redo if (defined($_)); # start loop over w/o reading next line
276 } elsif ($fScheme && m%^\(add-(boolean-style|window-style|window-hint)-option\s+\#:(\S+)%) {
277 my $filename = $ARGV;
279 ProcessWindowStyleOption($filename,$line,$1,$2);
280 } elsif ($fScheme && m%^\(define-public\s+([^\s\)]+)%) {
281 # this is for variables, so we don't need the optional * above
282 $last_public_scheme_definition = $1;
283 } elsif (m%(?:/|^;;;\s*)\*\*\s*(\w[^:\n\s]*)(?::\s*(.*?))?\s*$%) {
284 # matches a /** CHAPTER: DESCRIPTION (e.g., CONCEPT) comment
286 my $description = $2;
287 if (!defined($description) || $description eq "") {
288 if ($type eq "VAR" &&
289 $last_public_scheme_definition ne "") {
290 $description = $last_public_scheme_definition;
292 print STDERR "$ARGV:$.:**** Missing \": section\" in /**$type marker\n";
297 print STDERR "GOT $type, $description\n";
299 if ($type eq "" || $description eq "") {
300 print STDERR "$ARGV:$.:**** Improper /**-style comment: got type = \`$type\', description = \`$description\'.\n";
303 if (uc($type) eq "HOOK") {
304 my $filename = $ARGV;
306 my $body = ReadRestOfComment("",$fScheme);
307 print STDERR "**** $filename:$line: warning -- old-style HOOK documentation\n";
308 ProcessHookComment($filename, $line, $description,-1,$body);
309 } elsif (uc($type) eq "CONCEPT") {
310 my $filename = $ARGV;
312 my $body = ReadRestOfComment("",$fScheme);
313 ProcessConceptComment($filename, $line, $description,$body);
314 } elsif (uc($type) eq "VAR") {
315 my $filename = $ARGV;
317 my $body = ReadRestOfComment("",$fScheme);
318 ProcessVarComment($filename, $line, $description, $body);
320 print STDERR "$ARGV:$.:**** Unrecognized type for /**-style comment = \`$type\'\n";
324 $last_public_scheme_definition = "";
328 my $sgml_name = ""; # "$pkg_name.sgml";
331 $sgml_name .= ".sgml" if ($sgml_name !~ /\..+ml$/);
332 my $dir = dirname $sgml_name;
334 open (MARKUP_OUT,">$sgml_name") or die "Could not write to $sgml_name: $!";
335 chop (my $date = `date +"%d %B %Y"`);
336 chop (my $year = `date +"%Y"`);
338 $ref_header_sgml = ReadSgml("$dir/src/ref-header.sgml");
339 $ref_concepts_intro_sgml = ReadSgml("$dir/src/ref-concepts-intro.sgml");
340 $ref_hooks_intro_sgml = ReadSgml("$dir/src/ref-hooks-intro.sgml");
341 $ref_vars_intro_sgml = ReadSgml("$dir/src/ref-vars-intro.sgml");
342 $ref_proc_bygroup_intro_sgml = ReadSgml("$dir/src/ref-proc-bygroup-intro.sgml");
344 $ref_header_sgml =~ s/\@DATE\@/$date/g;
345 $ref_header_sgml =~ s/\@YEAR\@/$year/g;
346 $ref_header_sgml =~ s/\@VERSION\@/$scwm_version/g;
348 print MARKUP_OUT "$ref_header_sgml";
354 # Now output concepts chapter
356 DoneWindowStyleOptions(); # close the markup for window styles
358 print MARKUP_OUT <<START_CONCEPTS_CHAPTER
360 <title>Concepts</title>
361 <sect1 id="Concepts-Introduction"><title>Introduction</title>
362 $ref_concepts_intro_sgml
364 START_CONCEPTS_CHAPTER
366 foreach my $concept (sort { lc($a) cmp lc($b) } keys %concepts ) {
367 my $markup = $concepts{$concept}{markup};
368 my $concept_id = ScmIdToSgmlId($concept);
369 $concept_id =~ tr/ _/--/;
370 print MARKUP_OUT " <sect1 id=\"$concept_id\"><title>$concept</title><para>\n$markup
374 print MARKUP_OUT " </chapter>\n";
381 # Now output hooks chapter
383 print MARKUP_OUT <<START_HOOKS_CHAPTER
386 <sect1 id="Hooks-Introduction"><title>Introduction</title>
387 $ref_hooks_intro_sgml
391 foreach my $hook (sort { lc($a) cmp lc($b) } keys %hooks ) {
392 my $markup = $hooks{$hook}{markup};
393 my $numargs = $hooks{$hook}{numargs};
394 my $args_text = "$numargs args";
396 $args_text = "thunk";
397 } elsif ($numargs == 1) {
398 $args_text = "1 arg";
400 print MARKUP_OUT " <sect1 id=\"$hook\"><title>$hook ($args_text)</title><para>\n$markup
403 print MARKUP_OUT " </chapter>\n";
407 # Now output vars chapter
409 print MARKUP_OUT <<START_VARS_CHAPTER
411 <title>User variables</title>
412 <sect1 id="Vars-Introduction"><title>Introduction</title>
417 foreach my $var (sort { lc($a) cmp lc($b) } keys %vars ) {
418 my $markup = $vars{$var}{markup};
419 my $sgmlid = ScmIdToSgmlId($var);
420 print MARKUP_OUT " <sect1 id=\"$sgmlid\"><title>$var</title><para>\n$markup
423 print MARKUP_OUT " </chapter>\n";
432 # Now output primitives by defined-in file
434 print MARKUP_OUT <<START_PROC_BY_FILE
436 <title>Procedure Synopses by Group</title>
437 <sect1 id="Procs-ByGroup-Introduction"><title>Introduction</title>
438 $ref_proc_bygroup_intro_sgml
443 foreach my $file (sort { lc($a) cmp lc($b) } keys %file_funcs) {
444 my @prims = @{$file_funcs{$file}};
446 # keep only primitives that start with "F"
447 @prims = grep(m/^f/i, @prims);
449 if (scalar(@prims) > 0) {
450 print MARKUP_OUT " <sect1><title>$file</title> <itemizedlist>\n";
451 foreach my $proc (sort { $procedure{$a}{line} <=> $procedure{$b}{line} } @prims ) {
452 my $markup = $procedure{$proc}{markup};
453 my $target = $procedure{$proc}{sgml_id};
454 my $primname = $procedure{$proc}{primname};
455 my $purpose = $procedure{$proc}{purpose};
456 my $markup_purpose = $procedure{$proc}{markup_purpose};
457 print MARKUP_OUT " <listitem><para><link linkend=\"$target\"><function>$proc</function></link> -- $markup_purpose</para></listitem>\n";
459 print MARKUP_OUT " </itemizedlist> </sect1>\n";
462 print MARKUP_OUT " </chapter>\n";
468 print MARKUP_OUT <<END_CHAP_HEAD
470 <title>Procedures in Alphabetical Order</title>
475 # This outputs the scwm-procedures.txt file to stdout
476 foreach my $proc (sort { lc($a) cmp lc($b) } keys %procedure) {
477 my $usage = $procedure{$proc}{usage};
478 my $comment = $procedure{$proc}{comment};
479 my $file = $procedure{$proc}{file};
480 my $line = $procedure{$proc}{line};
481 my $markup = $procedure{$proc}{markup};
482 my $module = $procedure{$proc}{module};
492 next if $opt_F && $proc !~ m/^f/i;
493 print MARKUP_OUT $markup, "\n";
497 # End the procedures chapter
499 print MARKUP_OUT " </chapter>\n";
506 # Now output sgml trailer
508 print MARKUP_OUT <<END_TRAILER
510 <!-- Keep this comment at the end of the file
522 if ($opt_V || $opt_O) {
526 open(VAR_OUT,">$file") or
527 die "Could not open $file";
528 print STDERR "outputting user-options to $file\n";
533 open(VAR_DOC_OUT,">$file") or
534 die "Could not open $file";
535 print STDERR "outputting vars documentation to $file\n";
540 ;;; AUTOMATICALLY-GENERATED BY extract-docs; DO NOT EDIT!
541 (define-module (app scwm user-options))
543 ;; Each element of the list is \'(var-name comment setter getter)
544 \(define-public user-options \(list
548 foreach my $var (sort { lc($a) cmp lc($b) } keys %vars ) {
549 my $comment = $vars{$var}{comment};
550 my $file = $vars{$var}{file};
551 my $line = $vars{$var}{line};
552 my $module = $vars{$var}{module};
553 # Output information for a user-option variable
554 print VAR_OUT "\'($var)\n" if $opt_V;
557 print VAR_DOC_OUT <<EOC
569 print VAR_OUT "))\n";
579 open (HOOKS_OUT,">$file") or
580 die "Could not open $file";
581 print STDERR "outputting hooks to $file\n";
583 foreach my $hook (sort { lc($a) cmp lc($b) } keys %hooks ) {
584 my $comment = $hooks{$hook}{comment};
585 my $numargs = $hooks{$hook}{numargs};
586 my $file = $hooks{$hook}{file};
587 my $line = $hooks{$hook}{line};
588 my $module = $hooks{$hook}{module};
590 print HOOKS_OUT <<EOC;
591 $hook ($numargs args)
606 open (CONCEPTS_OUT,">$file") or
607 die "Could not open $file";
608 print STDERR "outputting concepts to $file\n";
610 foreach my $concept (sort { lc($a) cmp lc($b) } keys %concepts ) {
611 my $comment = $concepts{$concept}{comment};
612 my $file = $concepts{$concept}{file};
613 my $line = $concepts{$concept}{line};
615 print CONCEPTS_OUT <<EOC;
631 sub ProcessSchemeHeader( $$$$$ ) {
632 my ($filename, $module, $proc_name, $line, $header) = @_;
634 my ($type,$rest) = ($header =~ m%^\((define\*?-public)\s+(.*)%s);
635 # print STDERR "HEAD: $header\n";
636 # print STDERR "TYPE: $type\n";
637 # print STDERR "REST: $rest\n";
638 my ($name_and_args,$after_formals) = Text::Balanced::extract_bracketed($rest,'(');
639 # print STDERR "NAA: $name_and_args\n";
640 # print STDERR "AF: $after_formals\n";
641 my ($match) = ($name_and_args =~ m%\(\s*([^\s\)]+)%s);
642 my $comment = Text::Balanced::extract_delimited($after_formals,'"');
643 # print STDERR "COMMENT: $comment\n";
644 # print STDERR "---------------\n";
647 print STDERR "Handling $proc_name from $filename:$line\n";
650 if ($match ne $proc_name) {
651 print STDERR "$filename:$line:**** $match not same as $proc_name\n";
655 if ($match =~ m%^\(%) {
656 $proc_name = substr($match,1);
657 # print STDERR "$filename:$line:**** $proc_name is not an ordinary define -- returns a lambda?\n";
659 if (!defined($comment)) {
660 if ($fWarnIfNoComment) {
661 print STDERR "$filename:$line:**** $proc_name: could not find comment\n";
663 $comment = "No documentation supplied.";
665 # my $usage = sprintf "(%s%s%s)", $proc_name, ($#arglist >= 0? " ":""),
666 # join(" ",@arglist);
667 my $usage = $name_and_args;
668 # Remove leading and trailing quote
669 $comment =~ s/^\"(.*)\"$/$1/ms;
670 # Unquote quoted quotes (CRW:FIXME:GJB: are there other things we
671 # may need to unquote here?)
672 $comment =~ s/\\"/"/g;
673 # print STDERR "Scheme Proc: $proc_name\nArgs: @arglist\n$header\n";
674 # print STDERR "$usage\n\n$comment\n\n
\f\n";
676 my $purpose = PurposeFromComment($comment,$filename,$line,$proc_name);
678 my $sgml_id = ScmIdToSgmlId($proc_name);
680 my $markup_purpose = MarkupComment($purpose);
681 my $markup_usage = MarkupUsage($usage);
682 my $markup_comment = MarkupComment($comment);
683 my $markup = CreateMarkupBody($proc_name, $sgml_id, $module,
684 $markup_usage, $markup_purpose, $markup_comment,
687 $procedure{$proc_name} = { usage => $usage,
692 markup_purpose => $markup_purpose,
693 markup_usage => $markup_usage,
699 push @{$file_funcs{$filename}}, $proc_name;
704 # Currently only `define-string-matcher' is handled
705 sub ProcessSchemeExtension( $$$$$ ) {
706 my ($filename, $module, $ext_name, $line, $header) = @_;
708 my ($type,$rest) = ($header =~ m%^\(define-(string-matcher)\s+\S+\s+(.*)%s);
709 # print STDERR "Rest = $rest\n";
710 my $comment = Text::Balanced::extract_delimited($rest,'"');
713 print STDERR "Handling extension $ext_name from $filename:$line\n";
716 if (!defined($comment)) {
717 if ($fWarnIfNoComment) {
718 print STDERR "$filename:$line:**** $ext_name: could not find comment\n";
720 $comment = "No documentation supplied.";
723 my $usage = "($ext_name)";
725 if ($type eq "string-matcher") {
726 $usage = "($ext_name STRING TYPE CASE-SENSITIVE?)";
729 # Remove leading and trailing quote
730 $comment =~ s/^\"(.*)\"$/$1/ms;
731 # Unquote quoted quotes (CRW:FIXME:GJB: are there other things we
732 # may need to unquote here?)
733 $comment =~ s/\\"/"/g;
735 my $purpose = PurposeFromComment($comment,$filename,$line,$ext_name);
737 my $sgml_id = ScmIdToSgmlId($ext_name);
739 my $markup_purpose = MarkupComment($purpose);
740 my $markup_usage = MarkupUsage($usage);
741 my $markup_comment = MarkupComment($comment);
742 my $markup = CreateMarkupBody($ext_name, $sgml_id, $module,
743 $markup_usage, $markup_purpose, $markup_comment,
746 $procedure{$ext_name} = { usage => $usage,
751 markup_purpose => $markup_purpose,
752 markup_usage => $markup_usage,
758 push @{$file_funcs{$filename}}, $ext_name;
764 sub ProcessHeader( $$$ ) {
765 my ($filename, $line, $header) = @_;
766 # print STDERR "header = $header\n";
767 # print STDERR "filename = $filename\n";
768 my ($cprimname, $primname, $req, $opt, $var, $argslist,$space,$intspec,$comment_maybe) =
769 $header =~ m%^${prefix}_[IW]?PROC\s*\(\s*([^, \t]*),\s*\"([^\"]*)\"\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*\(([\d\s\w,()\n]*)\)(\s*),(?:[ \t]*([^\n]*?),\s*)?\s*(.*)$%so;
771 # print STDERR "argslist = $argslist\n";
772 if (!defined($comment_maybe)) {
773 $comment_maybe = $intspec;
775 # print STDERR "comment_maybe = $comment_maybe\n";
776 if (defined($space)) {
777 # print STDERR "$filename:$line:****WARNING: better to use )) to close arg list\n";
781 print STDERR "Handing $primname from $filename:$line\n";
784 if (!defined($cprimname)) {
785 print STDERR "$filename:$line:****ERROR:could not parse argument list\n";
788 my ($comment) = $comment_maybe =~ m%^\"(.*[^\\])\"\s*\)\s*\#define%s;
789 my ($fname_define) = $header =~ m%^\s*\#\s*define\s+FUNC_NAME\s+(.*?)\s*$%m;
792 if (defined($comment)) {
793 if ($comment =~ m/[^\\]\"/) {
794 print STDERR "$filename:$line:****ERROR:comment includes an un-escaped double-quote\n";
796 # Unquote quoted quotes (CRW:FIXME:GJB: are there other things we
797 # may need to unquote here?)
798 $comment =~ s/\\"/"/g;
799 # print STDERR "comment = $comment\n";
802 my $clean_argslist = $argslist;
803 $clean_argslist =~ s/\bARG_UNUSED\s*\(\s*([^\)]*)\s*\)/$1/g;
804 $clean_argslist =~ s/\bARG_IGNORE\s*\(\s*([^\)]*)\s*\)/$1/g;
805 # print STDERR "clean arglist = $clean_argslist\n";
806 my $cremovals = ($clean_argslist =~ s/\bSCM\b//g);
807 $clean_argslist =~ s/\n/ /g;
808 $clean_argslist =~ s/[ \t]+//g;
809 @argnames = split(/,/, $clean_argslist); # this array is for comparison w/ C code
810 $clean_argslist =~ s/_[pP]\b/?/g;
811 $clean_argslist =~ s/_[xX]\b/!/g;
812 $clean_argslist =~ s/_/-/g;
813 my @args = split(/,/, $clean_argslist);
815 # now create a hash of the names for testing words in the comment
816 # whether they are referring to formal parameters
817 my %argnames = map { uc($_) => 1} @args;
819 if (!CNameMatchesSchemeName($cprimname, $primname)) {
820 if (!$fReallyQuiet) {
821 print STDERR "$filename:$line:**** $cprimname: scheme primitive name `$primname' does not match `$cprimname'\n";
825 if ($cremovals != scalar(@args) ) {
826 print STDERR "$filename:$line:**** $cprimname: types inconsistency (all args should be type SCM)\n";
829 if (($req + $opt + $var) != scalar(@args) ) {
830 print STDERR "$filename:$line:**** $cprimname: argument inconsistency -- check #s of arguments\n";
833 if ($var != 0 && $var != 1) {
834 print STDERR "$filename:$line:**** $cprimname: number of variable arguments == $var -- why?\n";
837 my $fAlreadyWarnedAboutNoComment = FALSE;
839 if (!defined($comment) || $comment eq "") {
840 if (!$fReallyQuiet && $fWarnIfNoComment) {
841 $fAlreadyWarnedAboutNoComment = TRUE;
842 print STDERR "$filename:$line:**** $cprimname: comment missing\n";
843 # print STDERR "comment_maybe = $comment_maybe\n";
848 if (!defined($fname_define)) {
849 print STDERR "$filename:$line:**** $cprimname: \`#define FUNC_NAME s_$cprimname\' is missing\n";
850 } elsif ($fname_define ne "s_".$cprimname) {
851 print STDERR "$filename:$line:**** $cprimname: \`#define FUNC_NAME s_$cprimname\' does not match function name \`$fname_define\'\n";
854 my @required_args = @args[0..($req-1)];
855 my @optional_args = @args[$req..($req + $opt - 1)];
856 my @var_args = @args[($req+$opt)..($req+$opt+$var-1)];
858 my $arg_listing = "";
860 $arg_listing .= "@required_args";
861 if ($#optional_args >= 0) {
862 $arg_listing .= " #&optional @optional_args";
864 if ($#var_args >= 0) {
865 $arg_listing .= " . @var_args";
869 my $usage = sprintf "(%s%s%s)", $primname, ($arg_listing ne ""? " ":""),
872 my %upcase_words = ();
874 # check to make sure all all-uppercase words in the comment
876 foreach my $word (split /[^-+%?\$!\w_\"]+/, $comment) {
877 if ($word =~ /^[A-Z][-%A-Z0-9_+?!]+$/) {
878 if (!defined($argnames{$word})) {
879 next if $word eq "X11"; # do not require this word to be an argumet
880 if (!$fReallyQuiet) {
881 print STDERR "$filename:$line:**** $cprimname: all-uppercase word \`$word\' does not match an argument\n";
887 # check to make sure all formals are referred to in the comment
888 foreach my $formal (keys %argnames) {
889 if ($comment !~ /\Q$formal\E/) {
890 if (!$fReallyQuiet && $fWarnIfNoComment && !$fAlreadyWarnedAboutNoComment) {
891 print STDERR "$filename:$line:**** $cprimname: formal $formal not mentioned in comment\n";
896 my $purpose = PurposeFromComment($comment,$filename,$line,$cprimname);
898 # Clean up spacing in $comment -- use \n instead of $ since
899 # the latter matches before the new line
900 $comment =~ s/^\s*\n//mg;
901 # delete whitespace-only lines in old-comment, so we don't get a warning
902 # on them -- they're useful to avoid Emacs's reindent-paragraph
903 # from causing the synopsis sentence to have extra words tacked onto the end
905 my $old_comment = $comment;
907 $comment =~ s/^\s+//mg;
908 if ($comment ne $old_comment) {
910 print STDERR "$filename:$line:**** $cprimname: leading spaces (indentation) is being omitted\n";
914 # Clean up trailing space, but don't warn about it
915 $comment =~ s/\s+$//mg;
917 IspellText($filename,$line,$comment) if $opt_s;
919 my $sgml_id = $cprimname;
922 # Now want to do the markup of $comment, and set $markup_comment
923 # FIXGJB: fold into testing, above
924 my $markup_comment = MarkupComment($comment);
926 # Mark formals within comment with <parameter> tag
927 # must sort by length so longer formals get replaced first
928 # note that it is essential to convert to lowercase as we
929 # go, otherwise shorter substitutions will be made inside
930 # an already-substitued <param> </param> pair
931 foreach my $formal (sort { length($b) <=> length($a) } keys %argnames) {
932 $markup_comment =~ s%(\Q$formal\E)%<parameter>\L$1\E</parameter>%g;
935 # Just do simple markup of the usage
936 my $markup_usage = MarkupUsage($usage);
938 my $markup_purpose = MarkupComment($purpose);
940 my $module = "Built-in Primitive";
941 if ($filename =~ m%^modules/([^/]*)%) {
942 $module = "Primitive from (app scwm $1)";
945 my $markup = CreateMarkupBody($primname, $sgml_id, $module,
946 $markup_usage, $markup_purpose, $markup_comment,
951 scheme-primitive-name: $primname
952 C-primitive-name: $cprimname
953 arg kinds: $req, $opt, $var
954 args: @{[join(",",@args)]}
955 arg listing: $arg_listing
956 fname define: $fname_define
960 $procedure{$primname} = { usage => $usage,
965 markup_purpose => $markup_purpose,
966 markup_usage => $markup_usage,
971 push @{$file_funcs{$filename}}, $primname;
976 # remember, no underscores in sgml ids
977 sub ScmIdToSgmlId ( $ ) {
984 $id =~ s/[\?\!\_\$\^\&\/]/-/g;
989 sub PurposeFromComment ( $$$$ ) {
990 my ($comment,$filename,$line,$primname) = @_;
992 my ($purpose) = $comment =~ m%(.*?[\.;\n])%;
994 if (!defined $purpose) {
995 # did not match, so must have been a one-liner w/o a newline
998 chomp ($purpose); # in case it matched the newline
999 if (!defined($purpose) || $purpose !~ /\.\s*$/) {
1000 if (!$fQuiet && $fWarnIfNoComment && $purpose !~/^\s*$/) {
1001 print STDERR "$filename:$line:**** $primname: first line of comment should be a purpose sentence\n";
1007 # $comment is the part of the comment we've already read
1008 sub ReadRestOfComment ( $$ ) {
1009 my ($comment, $fScheme) = @_;
1011 while (defined($_ = <>) && m%^;;;\s*(.*)$%) {
1015 # FIXGJB: this might be wrong-- resetting too early?
1016 $current_module = "";
1020 # read rest of C comment
1022 if ($comment !~ m%\*/%) {
1023 while (defined($_ = <>) && $_ !~ m%\*/%) {
1027 # FIXGJB: this might be wrong-- resetting too early?
1028 $current_module = "";
1033 $comment =~ s%\*/\s*$%%s;
1038 sub ReadRestOfDocstring ( $ ) {
1039 my ($filename,$line,$docstring) = @_;
1040 while (defined($_ = <>) && $_ !~ m%[^\\]\"\s*\)\s*;\s*$%) {
1044 $current_module = "";
1048 $docstring =~ s%\"\s*\)\s*;\s*$%%m;
1049 if ($docstring =~ m/[^\\]\"/) {
1050 print STDERR "$filename:$line:****ERROR:docstring includes an un-escaped double-quote\n";
1052 # Unquote quoted quotes (CRW:FIXME:GJB: are there other things we
1053 # may need to unquote here?)
1054 $docstring =~ s/\\"/"/g;
1058 sub ProcessConceptComment ( $$$$ ) {
1059 my ($filename,$line,$description,$comment) = @_;
1060 $comment =~ s%\*/\s*$%%m;
1062 print STDERR "Concept \`$description\' with body = \n$comment\n";
1065 IspellText($filename,$line,$comment) if $opt_s;
1067 my $markup = MarkupComment($comment);
1069 $concepts{$description} = { comment => $comment,
1076 sub ProcessHookComment ( $$$$$ ) {
1077 my ($filename,$line,$description,$num_args,$comment) = @_;
1078 $comment =~ s%\*/\s*$%%m;
1080 print STDERR "Hook \`$description\' with body = \n$comment\n";
1083 IspellText($filename,$line,$comment) if $opt_s;
1085 my $markup = MarkupComment($comment);
1086 $hooks{$description} = { comment => $comment,
1088 numargs => $num_args,
1089 module => $current_module,
1095 sub ProcessVarComment ( $$$$ ) {
1096 my ($filename,$line,$description,$comment) = @_;
1097 $comment =~ s%\*/\s*$%%m;
1099 print STDERR "Var \`$description\' with body = \n$comment\n";
1102 IspellText($filename,$line,$comment) if $opt_s;
1104 my $markup = MarkupComment($comment);
1105 $vars{$description} = { comment => $comment,
1107 module => $current_module,
1115 sub CreateMarkupBody ( $$$$$$$ ) {
1116 my ($primname, $sgml_id, $module, $markup_usage, $markup_purpose, $markup_comment,
1117 $filename, $line) = @_;
1118 # Use <refentry>, <refname>, <refpurpose>, <synopsis>
1120 # Filename url links rely on environment variable SCWMDIR being
1121 # set to the base of the scwm distribution
1122 # i.e. $SCWMDIR/scwm/scwm.c should contain main()
1123 my $cvs_url_link = "$scwmdir/$filename";
1124 $cvs_url_link =~ s%/~checkout~%%;
1126 "<refentry id=\"$sgml_id\">
1128 <refname>$primname</refname>
1129 <refpurpose>$markup_purpose</refpurpose>
1132 <synopsis>$markup_usage</synopsis>
1135 <title>Description</title>
1140 <title>Implementation Notes</title>
1141 <para> Module: $module</para>
1142 <para> Defined in <ulink url=\"$scwmdir/$filename\"><filename>$filename</filename></ulink>
1143 at line $line (<ulink url=\"$cvs_url_link\">CVS log</ulink>)</para> </refsect2>
1152 sub MarkupUsage( $ ) {
1153 my ($markup_usage) = @_;
1154 $markup_usage =~ s%&(optional|key|allow-other-keys|rest)%&$1%g;
1155 $markup_usage =~ s%(\s+)&(\s+)%$1&$2%g;
1156 $markup_usage =~ s%(\s+)<(\s+)%$1<$2%g;
1157 $markup_usage =~ s%(\s+)>(\s+)%$1>$2%g;
1158 return $markup_usage;
1163 sub MarkupComment( $ ) {
1166 # convert & into & space-delimited <, > into < and >
1167 $body =~ s%&%&%g;
1168 $body =~ s%(\s+)<(\s+)%$1<$2%g;
1169 $body =~ s%(\s+)>(\s+)%$1>$2%g;
1171 # Mark #t and #f within comment with <literal> tag
1172 $body =~ s%(\#[tf])%<literal>$1</literal>%g;
1174 # Replace `procedure' with <function>procedure</function>
1175 $body =~ s%\`([-A-Za-z0-9_?!+\%&\$]+?)\'%
1176 "<link linkend=\"" . ScmIdToSgmlId($1) . "\"><function>$1</function></link>"%eg;
1181 sub IspellText( $$$ ) {
1182 my ($filename,$line,$text,$response) = @_;
1183 foreach my $word (split /[\d\W]+/, $text) {
1184 # ispell is picky about lots of stuff, so ignore them
1185 next if $word =~ /^[-\#]/;
1186 next if $word !~ /^\w\w+/;
1187 next if $word eq uc($word);
1188 print STDERR "ispell trying $word -> " if $fDebug;
1189 my $junk = <ISPELL_RESPONSE>; # read the blank
1190 print ISPELL $word, "\n";
1191 chomp (my $response = <ISPELL_RESPONSE>);
1192 print STDERR "response = \`$response\'\n" if $fDebug;
1193 if ($response eq "") {
1194 print STDERR "$filename:$line:**** ISPELL is out of sync (last word \`$word\') -- aborting its use!\n";
1198 if ($response !~ m/^[+\*]/) {
1199 print STDERR "$filename:$line:**** ispell reported possible misspelling: $word -> $response\n";
1200 print STDERR "Should I add `$word' to my list of known correct words? ";
1202 if ($response=~/^y(es)?$/i) {
1203 print ISPELL "*",lc($word),"\n";
1209 sub CNameMatchesSchemeNameIgnoreCase( $$ ) {
1213 CNameMatchesSchemeName($a,$b);
1216 sub CNameMatchesSchemeName( $$ ) {
1217 my ($cprimname, $primname) = @_;
1219 # now convert the c function name into the expected (preferred) primitive name:
1220 my $expected_primname = $cprimname;
1221 $expected_primname =~ s/_[pP]\b/?/g;
1222 $expected_primname =~ s/_[xX]\b/!/g;
1223 $expected_primname =~ s/\bpct_/%/g;
1224 $expected_primname =~ s/_/-/g;
1225 # alternative possibility (ignoring chance of multiple to's in string)
1226 my $expected_primname2 = $expected_primname;
1227 $expected_primname2 =~ s/-to-/->/g;
1229 return ($primname eq $expected_primname || $primname eq $expected_primname2);
1233 my ($filename) = @_;
1235 open(IN,"<$filename") || die "Could not open $filename: $!";
1242 sub ProcessWindowStyleOption( $$ ) {
1243 my ($file,$line,$kind,$option) = (@_);
1245 print STDERR "Got window style option for $kind named $option\n";
1248 my $winstyle = "Window Style"; # from face.c's embedded CONCEPT comment
1250 my $comment = $concepts{$winstyle}{comment};
1251 my $markup = $concepts{$winstyle}{markup};
1253 if (!$fAddedStyleHeader) {
1256 <title>window style options</title>
1257 <tgroup align="char" cols="3">
1259 <entry>Option</entry> <entry>Type</entry> <entry>Implementation</entry>
1263 $fAddedStyleHeader = TRUE;
1266 $comment .= "$option ($kind) from $file:$line\n";
1269 "<entry/" . MarkupComment($option) . "/ " .
1270 "<entry/" . MarkupComment($kind) . "/ " .
1271 "<entry>$file:$line</entry> " .
1274 $concepts{$winstyle}{comment} = $comment;
1275 $concepts{$winstyle}{markup} = $markup;
1278 sub DoneWindowStyleOptions() {
1279 my $winstyle = "Window Style"; # from face.c's embedded CONCEPT comment
1281 $concepts{$winstyle}{markup} .= "</tbody></tgroup></table>\n";
1284 sub check_arg_name_number_match ( $$\@ ) {
1285 my $validate_fn = shift;
1287 my $argname = shift;
1288 my $aref_argnames = shift;
1291 print STDERR "Checking ", $argname, " (arg ", $argnum, ") against list: ", join(" ",@$aref_argnames), "\n"
1293 if ($argnum > scalar(@$aref_argnames) ||
1294 $argnames[$argnum-1] ne $argname) {
1295 print STDERR "$ARGV:$.:**** Argument name/number mismatch in $validate_fn line\n";