version 1.18
[cppi.git] / src / cpp-indent.pl
blob3c5ad14ed1ad3f9b6af78b2b3ee4091f12ca8492
1 #!@PERL@ -w
2 # Filter C code so that CPP #-directives are indented to reflect nesting.
3 # written by Jim Meyering
5 # This code is included here solely to provide a little perspective
6 # on the development process and evolution of the package. The Lex/C
7 # version is much more efficient.
9 use strict;
11 my $checking = 0;
12 if (@ARGV && $ARGV[0] eq '-c')
14 shift @ARGV;
15 $checking = 1;
17 unshift (@ARGV, '-') if @ARGV == 0;
19 die "usage: $0 [FILE]\n" if @ARGV > 1;
21 my $file = shift @ARGV;
22 my $exit_status = cpp_indent ($file, $checking);
24 exit ($exit_status);
26 # ===============================
28 # Return 2 for syntax problems.
29 # Return 1 for invalid indentation of CPP #-directives (only if $checking).
30 # if checking
31 # return 0 if syntax and indentation are valid
32 # else
33 # if (syntax is valid)
34 # {
35 # print properly indented code to stdout
36 # return 0
37 # }
38 sub cpp_indent ($$)
40 my ($file, $checking) = @_;
41 my $start_of_last_comment_or_string;
43 sub IN_CODE {1}
44 sub IN_COMMENT {2}
45 sub IN_STRING {3}
46 sub update_state ($$)
48 my ($state, $line) = @_;
50 my $new_string_or_comment = 0;
51 while ($line)
53 #warn "s: $state: line=$line";
54 my $remainder = '';
55 if ($state == IN_CODE)
57 my $i = index ($line, '"');
58 my $j = index ($line, '/*');
59 last if ($i < 0 && $j < 0);
61 # A double quote inside single quotes doesn't count.
62 $i = -1 if substr ($line, $i - 1, 1) eq "'";
64 my $k;
65 if ($i < 0)
67 last if $j < 0;
68 $k = $j;
70 else
72 if ($j < 0)
74 $k = $i;
76 else
78 $k = ($i < $j ? $i : $j);
81 $state = ($k == $i ? IN_STRING : IN_COMMENT);
82 $remainder = substr ($line, $k + 1);
83 $new_string_or_comment = 1;
85 elsif ($state == IN_COMMENT)
87 if ($line =~ m!\*/!g)
89 $state = IN_CODE;
90 $remainder = $';
93 else # $state == IN_STRING
95 if ($line =~ m!\\*\"!)
97 $state = IN_CODE if (length ($&) % 2 == 1);
98 $remainder = $';
101 $line = $remainder;
104 return ($state, $new_string_or_comment);
106 # ===============================================================
108 # TODO: allow this to be overridden by a command-line option.
109 my $indent_incr = ' ';
111 my @opener_stack;
112 my $depth = 0;
114 open (FILE, $file) || die "$0: couldn't open $file: $!\n";
116 my $fail = 0;
117 my $state = IN_CODE;
118 my $line;
119 while (defined ($line = <FILE>))
121 my $rest;
123 if ($state == IN_CODE)
125 my $saved_line = $line;
126 if ($line =~ s/^\s*\#\s*//)
128 my $keyword;
129 my $indent;
130 my $pfx = "$0: $file: line $.";
131 if ($line =~ /^if\b/
132 || $line =~ /^ifdef\b/
133 || $line =~ /^ifndef\b/)
135 # Maintain stack of (line number, keyword) pairs to better
136 # report any `unterminated #if...' errors.
137 push @opener_stack, {LINE_NUMBER => $., KEYWORD => $&};
138 $keyword = $&;
139 $indent = $indent_incr x $depth;
140 ++$depth;
142 elsif ($line =~ /^(else|elif)\b/)
144 if ($depth < 1)
146 warn "$pfx: found #$& without matching #if\n";
147 $depth = 1;
148 $fail = 2;
150 $keyword = $&;
151 $indent = $indent_incr x ($depth - 1);
153 elsif ($line =~ /^endif\b/)
155 if ($depth < 1)
157 warn "$pfx: found #$& without matching #if\n";
158 $depth = 1;
159 $fail = 2;
161 $keyword = $&;
162 --$depth;
163 $indent = $indent_incr x $depth;
164 pop @opener_stack;
166 else
168 # Handle #error, #include, #pragma, etc.
169 $keyword = '';
170 $indent = $indent_incr x $depth;
173 if ($checking && $saved_line ne "#$indent$keyword$'")
175 warn "$pfx: not properly indented\n";
176 close FILE;
177 return 1;
180 $line = "#$indent$keyword$'";
181 $rest = $';
183 else
185 $rest = $line;
188 else
190 $rest = $line;
192 #print $state if !$checking;
193 print $line if !$checking;
195 my $new_non_code;
196 ($state, $new_non_code) = update_state ($state, $rest);
197 $start_of_last_comment_or_string = $.
198 if $new_non_code;
200 close FILE;
202 if ($depth != 0)
204 my $x;
205 foreach $x (@opener_stack)
207 warn "$0: $file: line $x->{LINE_NUMBER}: "
208 . "unterminated #$x->{KEYWORD}\n";
210 $fail = 2;
213 if ($state != IN_CODE)
215 my $type = ($state == IN_COMMENT && 'comment' || 'string');
216 warn "$0: $file: line $start_of_last_comment_or_string "
217 . "unterminated $type\n";
218 $fail = 2;
221 return $fail;