-
Notifications
You must be signed in to change notification settings - Fork 146
/
find-file-doc-comments.pl
executable file
·152 lines (127 loc) · 5.79 KB
/
find-file-doc-comments.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env perl
# find-file-doc-comments.pl: Find the doc comments for a file.
# Usage: find-file-doc-comments.pl <C source file name>
# This file is part of Elixir, a source code cross-referencer.
#
# By Christopher White <[email protected]>
# Copyright (c) 2019--2020 D3 Engineering, LLC.
#
# Elixir is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Elixir is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Elixir. If not, see <http://www.gnu.org/licenses/>.
use 5.010001;
use strict;
use warnings;
use autodie;
my $VERBOSE = $ENV{V};
exit main(@ARGV);
sub main {
die "Need a filename" unless @_;
my $filename = shift;
say "Processing file $filename" if $VERBOSE;
# Fatalize all warnings, and log which file triggered the warning.
local $SIG{__WARN__} = sub {
die "While processing $filename: $_[0]\n";
};
# Do `script.sh parse-defs` on the file
my @ctags = qx{ ctags -x --c-kinds=+p-m --language-force=C "$filename" |
grep -av "^operator " |
awk '{print \$1" "\$2" "\$3}' };
die "Could not get ctags: $!" if $!;
say "No ctags results" if $VERBOSE && !@ctags;
return 0 unless @ctags;
# Make a list of [name, type, line] arrays
my @ctags_parsed = map { [split] } @ctags;
# Flip it around to index functions and types by line. Don't index anything
# by name, since there can be multiple names with different types/lines (#186).
my %definition_lines;
my %definition_types;
for my $tag (@ctags_parsed) {
$definition_lines{$tag->[2]} = $tag->[0];
$definition_types{$tag->[2]} = $tag->[1] // '<none>';
}
if($VERBOSE) {
for my $tag (sort { $a->[2] <=> $b->[2] } @ctags_parsed) {
say $tag->[2], ': ', $tag->[0], ' is a(n) ', $tag->[1];
}
}
# Read the source file
open my $fh, '<', $filename;
my @source_lines = (undef, <$fh>);
# undef => indices in @source_lines match ctags's 1-based linenos
close $fh;
# Work backwards through the file and look for doc comments
my %doc_comments;
my $doc_comment_opener = qr{^\h*\/\*\*(?:\h|$)}; # Start of doc comment
my $comment_leader = qr{\h+\*\h+(?:(?:struct|enum|union|typedef)\h+)?};
for(my $lineno = $#source_lines ; $lineno >= 1 ; --$lineno) {
next unless exists $definition_lines{$lineno};
my $definition_name = $definition_lines{$lineno};
my $definition_type = $definition_types{$lineno};
say "\nChecking $definition_type $definition_name @ $lineno" if $VERBOSE;
# Comment header: be liberal in what we accept. For example, do not
# check the type of the definition/declaration against the type in
# the comment header. I don't think this will be a problem.
my $this_doc_comment_header =
qr{^$comment_leader\Q$definition_name\E(?:\h|\(|:|$)};
say " Regex is -$this_doc_comment_header-" if $VERBOSE;
# Make sure we get back past the first line of multiline definitions
if($definition_type eq 'macro') {
--$lineno while $lineno && $source_lines[$lineno] !~ /^\h*#\h*define/;
} elsif($definition_type eq 'function') {
# Try to handle the case of "int\nfoo()"
if($source_lines[$lineno] =~ /^\h*\Q$definition_name\E\b/) {
--$lineno while $lineno && $source_lines[$lineno] =~ /^[a-z_]/i;
}
}
# Assume cflags gave us the first line of the definition, or we got
# back to it manually. Move to the first line that might be a doc comment.
--$lineno;
# TODO make sure we're not still in the definition.
# E.g., memblock.h:for_each_mem_range(). The defintion is reported
# on the second line of the #define, not the first line.
say " Starting search for docs at line $lineno" if $VERBOSE;
# Find the last line that could be a doc-comment header
# for this function.
while($lineno && $source_lines[$lineno] =~
qr{
^\h*$ # Empty line
| ^\h+\*\/ # End of comment
| ^\h+\*(?:\h|$) # Continuation of comment
| $this_doc_comment_header
}x) {
if($VERBOSE) {
my $line = $source_lines[$lineno];
chomp $line;
say " skipped $lineno '$line'";
}
--$lineno;
}
++$lineno; # Check the last line that matched,
# because we may have just skipped past $this_doc_comment_header
# Is it actually a header for this function?
say " Checking line $lineno for header" if $VERBOSE;
next unless $source_lines[$lineno] =~ $this_doc_comment_header;
# We have found a header. Confirm it's a doc comment.
--$lineno;
next unless $lineno > 0 && $source_lines[$lineno] =~ $doc_comment_opener;
say " * Match" if $VERBOSE;
# We have found a doc comment for this function! Record it.
push @{$doc_comments{$definition_name}}, $lineno;
} # foreach line in reverse order
# Report the doc comments for each function
for my $definition_name (keys %doc_comments) {
my $comment_lines = $doc_comments{$definition_name};
say "$definition_name $_" foreach @$comment_lines;
}
return 0;
} #main