File Coverage

blib/lib/Hardware/Vhdl/Tidy.pm
Criterion Covered Total %
statement 141 197 71.5
branch 88 120 73.3
condition 53 102 51.9
subroutine 12 13 92.3
pod 3 3 100.0
total 297 435 68.2


line stmt bran cond sub pod time code
1             package Hardware::Vhdl::Tidy;
2            
3             # TO DO:
4             # Tidier directives in source code to set stack
5             # setting to control whether we include whitespace at start of empty lines
6             # check whether there are any other 'loop' forms
7             # put underscore at start of names of internal routines
8             # implement PBP generally
9            
10 4     4   181010 use Hardware::Vhdl::Lexer;
  4         81931  
  4         132  
11 4     4   4952 use Getopt::Long;
  4         53676  
  4         25  
12 4     4   686 use Carp;
  4         13  
  4         261  
13 4     4   23 use Exporter 'import';
  4         7  
  4         93  
14            
15 4     4   23 use strict;
  4         8  
  4         125  
16 4     4   20 use warnings;
  4         8  
  4         11464  
17            
18             sub parse_commandline;
19             sub tidy_vhdl_file;
20             sub tidy_vhdl;
21            
22             our $VERSION = 0.80;
23            
24             #our @EXPORT=();
25             our @EXPORT_OK=qw/ tidy_vhdl_file tidy_vhdl /;
26            
27             our $debug = 0;
28            
29             our %default_args = (
30             indent_spaces => 4, # integer value, >= 0
31             cont_spaces => 2, # integer value, >= 0
32             tab_spaces => 0, # integer value, >= 0
33             starting_indentation => 0, # integer value, >= 0
34             preprocessor_prefix => '#', # string
35             indent_preprocessor => 0, # boolean
36             );
37            
38             sub parse_commandline {
39             # parse command-line args
40             # for example, for an in-place tidy of a vhd file:
41             # perl -MHardware::Vhdl::Tidy -e "Hardware::Vhdl::Tidy::parse_commandline" -- -b <$file>
42 17     17 1 42693 my $inplace = 0;
43 17         34 my $bext = '.bak';
44 17         124 my %args = %default_args;
45 17         166 my $result = GetOptions(
46             "b" => \$inplace,
47             "bext=s" => \$bext,
48             "i|indentation=i" => \$args{indent_spaces},
49             "ci|continuation-indentation=i" => \$args{cont_spaces},
50             "t|tab_spaces=i" => \$args{tab_spaces},
51             "sil|starting-indentation-level=i" => \$args{starting_indentation},
52             "ppp|preprocessor-prefix=s" => \$args{preprocessor_prefix},
53             "ipp|indent-preprocessor" => \$args{indent_preprocessor},
54             );
55            
56             # any args not matched are taken to be input filenames
57 17         13349 for my $afile (@ARGV) {
58 17 100       48 if ($inplace) {
59             # change in-place: rename the original file and then make the old filename the destination
60 2   50     216 rename $afile, $afile . $bext || die "Could not rename $afile: $!\n";
61 2         16 tidy_vhdl_file( source => $afile . $bext, destination => $afile, %args );
62             } else {
63             # not in-place: output to STDOUT
64 15         64 tidy_vhdl_file( source => $afile, %args );
65             }
66             }
67            
68 17         78 return;
69             }
70            
71             sub tidy_vhdl_file {
72             # reads from STDIN if source filename not specified
73             # writes to STDOUT if destination filename not specified
74 17 50   17 1 113 my %args = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  0         0  
75            
76 17         24 my $fhi;
77 17 50       43 if ( defined $args{source} ) {
78 17   50     647 open $fhi, '<', $args{source} || die "Could not read $args{source}: $!\n";
79             } else {
80 0         0 open $fhi, '-' || die "Could not read from STDIN: $!\n";
81             }
82 17         40 binmode $fhi;
83 17         35 $args{source} = $fhi;
84            
85 17         25 my $fho;
86 17 100       52 if ( defined $args{destination} ) {
87 2   50     178 open $fho, '>', $args{destination} || die "Could not write $args{destination}: $!\n";
88             } else {
89 15         109 open $fho, '>-' || die "Could not write to STDOUT: $!\n";
90             }
91 17         38 binmode $fho;
92 17         45 $args{destination} = $fho;
93            
94 17         26 eval {
95 17         44 tidy_vhdl(\%args);
96             };
97 17 50       818 if ($@) {
98 0         0 my $err=$@;
99 0         0 $err =~ s/ tidy_vhdl /tidy_vhdl_file/xmsg;
100 0         0 croak $err;
101             }
102 17         556 return;
103             }
104            
105             # label is name end_t end_name/label
106             # entity n y y o o
107             # architecture n y y o o
108             # configuration n y y o o
109             # package [body] n y y o o
110             # function n y y o o
111             # procedure n y y o o
112            
113             # component n o y y o
114             # for (in config) n n u y n
115            
116             # case o y n y o
117             # process o o n y o
118             # if (...then) o n n y o
119             # for (...loop) o n n y o
120             # loop o n n y o
121            
122             # block y o n y o
123             # if (...generate) y n n y o
124             # for (...generate) y n n y o
125            
126             # NB: functions can be marked as pure or impure
127             # processes can be marked as postponed
128            
129             sub tidy_vhdl {
130             # parse and check arguments
131 29 50   29 1 11138 my %args = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  29         155  
132 29 100       328 croak "tidy_vhdl requires a 'source' parameter" unless defined $args{source};
133 28 100       181 croak "tidy_vhdl requires a 'destination' parameter" unless defined $args{destination};
134 27         94 for my $opt (keys %default_args) {
135 162 100       338 if ( !defined $args{$opt} ) { $args{$opt} = $default_args{$opt} }
  60         134  
136             }
137            
138 27         54 my $output_func;
139             {
140 27         32 my $outobj = $args{destination};
  27         52  
141 27         48 my $outtype = ref $outobj;
142 27 50 0     126 if ( $outtype eq q{} ) {
    100          
    100          
    100          
    50          
    0          
143 0         0 croak "tidy_vhdl 'destination' parameter is not of a valid type (it is not a reference)";
144             } elsif ( $outtype eq 'GLOB' ) {
145 241     241   430 $output_func = sub { print $outobj shift }
146 18         86 } elsif ( $outtype eq 'SCALAR' ) {
147 11     11   22 $output_func = sub { $$outobj .= shift }
148 1         6 } elsif ( $outtype eq 'ARRAY' ) {
149 15     15   30 $output_func = sub { push @$outobj, shift }
150 7         37 } elsif ( $outtype eq 'CODE' ) {
  0         0  
151 1         3 $output_func = $outobj;
152             } elsif (eval {$outobj->can('addtokens')} && !$@) {
153 0     0   0 $output_func = sub { $outobj->addtokens(shift) }
154 0         0 } else {
155 0         0 croak "tidy_vhdl 'destination' parameter is not of a valid type (type is '$outtype')";
156             }
157             }
158            
159 27         40 my $lexer;
160 27         33 eval {
161 27         257 $lexer = Hardware::Vhdl::Lexer->new({ linesource => $args{source} });
162             };
163 27 100       9436 if ($@) {
164 4         11 my $err=$@;
165 4         20 $err =~ s/ Hardware::Vhdl::Lexer->new /tidy_vhdl/xmsg;
166 4         16 $err =~ s/ linesource /source/xmsg;
167 4         402 croak $err;
168             }
169            
170 23         51 my $indent = $args{starting_indentation}; # current indentation level
171 23         36 my $bracks = 0; # how many () brackets deep are we?
172 23         36 my $line = ''; # current line of code tokens (a syntax line, nothing to do with newlines)
173 23         31 my @stack; # a list of the indented things we are inside
174 23         29 my $ln = 1; # source line num
175 23         26 my @outline; # list of tokens to go on the output line
176 23         30 my ( $token, $type );
177 23   66     87 while ( ( ( $token, $type ) = $lexer->get_next_token ) && defined $type ) {
178             #print "\n# input token type $type, '".&escape($token)."'\n";
179 1498         56268 my $indnext = 0;
180 1498         1631 my $bracknext = 0;
181 1498         1892 my $linestart = $line eq ''; # is this the first token of a syntax line?
182 1498 100       3026 my $toplevel = @stack ? $stack[0][0] : '';
183 1498 100       2776 my $botlevel = @stack ? $stack[-1][0] : '';
184 1498 100       3376 if ( substr( $type, 0, 1 ) eq 'c' ) {
185 683 100 66     4075 if ( @outline == 0 && $args{preprocessor_prefix} ne '' && substr($token,0,length $args{preprocessor_prefix}) eq $args{preprocessor_prefix}) {
    50 100        
    50          
    50          
186             # this is a preprocessor line: don't attempt to understand it, just emit the whole line unchanged
187 21         26 my $t;
188 21   33     54 while ( ( ( $t, $type ) = $lexer->get_next_token ) && defined $type) {
189 77         2934 my $lastchar = substr($token, -1, 1);
190 77         91 $token .= $t;
191 77 100       225 if ($type eq 'wn') {
192 21 50       38 if ($lastchar eq "\\") {
193 0         0 $ln++;
194             }
195             else {
196 21         32 last;
197             }
198             }
199             }
200 21         35 $type = 'pp';
201             }
202 0         0 elsif ( $token eq '(' ) { push @stack, [ '(', $ln ]; $indnext = 1; $bracknext = 1 }
  0         0  
  0         0  
203 0         0 elsif ( $token eq ')' ) { pop @stack; $indent--; $bracks-- }
  0         0  
  0         0  
204             elsif ( $bracks == 0 ) {
205 662         824 my $lctoken = lc $token;
206 662 50       1426 $line .= ( $lctoken =~ m!^\\.*\\$! ) ? 'xid ' : $lctoken . ' ';
207            
208 662 100 100     13850 if ( $lctoken eq ';' ) {
    100 33        
    100 33        
    50 66        
    100 33        
    50 66        
    100 100        
    100 66        
    50 33        
    50 33        
    50 100        
    100 100        
    100 33        
    50 33        
    50 33        
    50 33        
    100 100        
    50 33        
    50 33        
209 102 50 33     893 if (
      33        
      33        
      33        
      33        
210             # configuration spec: 'for' closed by a ';' rather than an 'end'
211             ( $toplevel eq 'architecture' && $line =~ /^for .* : / )
212             # a function declaration is completed by "return ;"
213             || ( $line =~ /^(pure |impure |)function \S+ return .* ; $/ && $line !~ / is / )
214             # a procedure declaration is completed by a ";" after the procedure name and optional parameter list
215             || ( $line =~ /^procedure \S+ ; $/ )
216             # an access type declaration is closed by a ';'
217             || ( $botlevel eq 'type-access')
218             ) {
219 0         0 pop @stack;
220 0         0 $indnext--;
221             }
222             # semicolon always finishes a syntax line
223 102         158 $line = '';
224             }
225            
226             # standard 'end' completes an indented section
227             elsif ( $lctoken eq 'end' && $linestart ) {
228 27 50       69 if ( $botlevel eq 'case=>' ) { pop @stack; $indent--; }
  0         0  
  0         0  
229 27         26 pop @stack;
230 27         61 $indent--;
231             }
232            
233             # 'begin' and 'elsif' give a temporary outdent, and finish a syntax line
234 21         34 elsif ( $lctoken =~ /^(begin|elsif)$/ ) { $indent--; $indnext = 1; $line = ''; $linestart = 1; }
  21         31  
  21         27  
  21         32  
235             # 'else' gives a temporary outdent, but check we are in an if/then rather than a "x<=y when..."
236             elsif ( $lctoken eq 'else' && @stack && $botlevel eq 'if' ) {
237 0         0 $indent--;
238 0         0 $indnext = 1;
239 0         0 $line = '';
240             }
241            
242             # 'is' finishes a syntax line if associated with an indenting token that takes an 'is'
243             elsif ( $lctoken eq 'is'
244             && $line =~
245             /^ (entity|architecture|configuration|package|((im)?pure \s )?function|procedure) \s /xms ) {
246 1         3 $line = '';
247             } elsif ( $lctoken eq 'is' && $line =~ /^(\S+ : )?case / ) {
248 0         0 $line = '';
249             } elsif ( $lctoken ne 'is'
250             && $line =~ /^ (\S+ \s : \s )?(component|block|(postponed \s )?process) \s (is \s )?\S+ \s $/xms ) {
251             # this is meant to deal with the case where an optional 'is' is missing -
252             # but it also messes up recognition of component instantiations with the 'component' keyword included
253 1         3 $linestart = 1;
254 1         4 $line = $lctoken . ' ';
255             }
256             # 'loop' finishes a syntax line if associated with an indenting 'for' or 'while'
257             elsif ( $lctoken eq 'loop' && $line =~ /^(\S+ : )?(for|while) / ) {
258 6         11 $line = '';
259             }
260             # in a configuration declaration or specification, a 'use' starts a new syntax line
261 0         0 elsif ( $lctoken eq 'use' ) { $linestart = 1 }
262             # 'then' or 'generate' finishes a syntax line
263 0         0 elsif ( $lctoken =~ /^ (then|generate) $/xms ) { $line = '' }
264            
265             # in a configuration declaration, a 'for' always starts a new syntax line and indents,
266             # unless it's an 'end for';
267             elsif ( $lctoken eq 'for'
268             && $toplevel eq 'configuration'
269             && $line !~ /^end for $/
270             && $line !~ / end for $/ ) {
271 0         0 push @stack, [ $lctoken, $ln, $2 ];
272 0         0 $indnext = 1;
273 0         0 $linestart = 1;
274 0         0 $line = 'for ';
275             }
276             # endable, indenting keywords which start a syntax line (optional label allowed)
277             elsif ( $lctoken =~ /^(case|if|for|while|loop)$/ && $line =~ /^((\S+) : )?\S+ $/ ) {
278 6         19 push @stack, [ $lctoken, $ln, $2 ];
279 6         8 $indnext = 1;
280 6 50       18 if ($lctoken eq 'loop') { $line = '' }
  0         0  
281             } elsif ( $lctoken eq 'process' && $line =~ /^((\S+) : )?(postponed )?process $/ ) {
282 21         72 push @stack, [ $lctoken, $ln, $2 ];
283 21         37 $indnext = 1;
284             }
285            
286             # code to be executed when a case option is matched
287             elsif ( $lctoken eq '=>' && $botlevel eq 'case' && $line =~ /^when / ) {
288 0         0 push @stack, [ 'case=>', $ln ];
289 0         0 $indnext = 1;
290 0         0 $line = '';
291             }
292             # the end of the code to be executed when a case option is matched, start of another option
293             elsif ( $lctoken eq 'when' && $linestart && $botlevel eq 'case=>' ) {
294 0         0 pop @stack;
295 0         0 $indent--;
296             }
297            
298             # endable, indenting keywords which start a syntax line (no label allowed)
299 0         0 elsif ( $line =~ /^(im)?pure function $/ ) { push @stack, [ $lctoken, $ln ]; $indnext = 1; }
  0         0  
300             elsif ( $lctoken =~
301             /^(entity|architecture|configuration|package|function|procedure|component|units)$/
302             && $linestart ) {
303 2         6 push @stack, [ $lctoken, $ln ];
304 2         3 $indnext = 1;
305             }
306             # endable, indenting keywords which start a syntax line (label required)
307             elsif ( $lctoken =~ /^(block)$/ && $line =~ /^(\S+) : \S+ $/ ) {
308 0         0 push @stack, [ $lctoken, $ln, $1 ];
309 0         0 $indnext = 1;
310             }
311            
312             elsif ( $line =~ /^type / && $lctoken =~ /^(access|units|record)$/) {
313 0         0 push @stack, [ 'type-'.$lctoken, $ln, $1 ];
314 0         0 $indnext = 1;
315 0         0 $line = '';
316             }
317            
318             }
319            
320 683 50       1263 if ( $indent < 0 ) { $indent = 0; warn "negative indent, source line $ln" }
  0         0  
  0         0  
321 683 50       1036 if ( $bracks < 0 ) { $bracks = 0; warn "negative bracket count, source line $ln" }
  0         0  
  0         0  
322            
323 683 50       1218 if ( $debug & 1 ) {
324             # debug dump
325 0         0 print "# ";
326 0         0 print " " x $indent;
327 0 0 0     0 print " " if $bracks == 0 && !$linestart;
328 0         0 print $token;
329 0         0 print " \t\t\tstart=$linestart stack=" . join( ', ', map { $_->[0] . '@' . $_->[1] } @stack );
  0         0  
330 0         0 print " line='$line'";
331 0         0 print "\n";
332             }
333             }
334            
335 1498 100       2692 if ( @outline == 0 ) {
336 527 100       916 if ( $type ne 'ws' ) {
337             #print "# emitting indent and token '".&escape($token)."'\n";
338 278 100 100     645 if ($type eq 'pp' && !$args{indent_preprocessor}) {
339             # preprocessor command: left-align
340 15         32 @outline = ( $token );
341             } else {
342             # work out the number of spaces to indent by
343 263         432 my $nsp = $indent * $args{indent_spaces};
344 263 100 66     976 $nsp += $args{cont_spaces} if $bracks == 0 && !$linestart;
345             # create a tab+space sequence to give the correct indent
346 263         253 my $ws;
347 263 100       427 if ( $args{tab_spaces} > 0 ) {
348 60         180 $ws = ( "\t" x int( $nsp / $args{tab_spaces} ) ) . ( ' ' x ( $nsp % $args{tab_spaces} ) );
349             } else {
350 203         353 $ws = ' ' x $nsp;
351             }
352 263         625 @outline = ( $ws, $token );
353             }
354             }
355             } else {
356             #print "# emitting token '".&escape($token)."'\n";
357 971         1578 push @outline, $token;
358             }
359 1498 100       4592 if ( $type =~ /^(wn|pp)$/ ) {
360 277         884 &$output_func( join( '', @outline ) );
361 277         361 $ln++;
362 277         563 @outline = ();
363             }
364            
365 1498         1710 $indent += $indnext;
366 1498         4413 $bracks += $bracknext;
367             }
368 22 100       501 &$output_func( join( '', @outline ) ) if @outline;
369 22 50       58 print "\n" if $debug;
370 22         152 return;
371             }
372            
373             1;
374            
375             __END__