#---------------------------------------------------------------------- # # convert_tabcomplete.pl # Crude hack to help convert tab-complete's else-if chain to a switch. # # Lines looking like # else if (Matches("A", "B")) # are converted to # case Matches("A", "B"): # with insertion of a preceding "break;". # The function name can be any one of Matches, HeadMatches, TailMatches, # MatchesCS, HeadMatchesCS, or TailMatchesCS. # # There is a limited ability to deal with AND conditions # else if (Matches("A", "B") && othercondition) # by converting that to # case Matches("A", "B"): # if (othercondition) # Also, simple OR conditions such as # else if (Matches("A", "B") || Matches("C", "D")) # are converted to adjacent case labels. # # Manual intervention is needed at the beginning and end of the switch # construct, and for any else-ifs that the script fails to handle. # There are a couple of places in the existing tab-complete.c file # that have to be adjusted to prevent "can't decipher" complaints # before the output is usable. # # If the input file is named something.c, the output file will be # named something.out.c. #---------------------------------------------------------------------- use strict; use warnings FATAL => 'all'; use Getopt::Long; my $output_path = ''; GetOptions('output:s' => \$output_path) || usage(); my $input_file = shift @ARGV || die "No input file.\n"; # Make sure output_path ends in a slash if needed. if ($output_path ne '' && substr($output_path, -1) ne '/') { $output_path .= '/'; } $input_file =~ /([\w.-]+)\.c$/ || die "Input file must be named something.c.\n"; my $base_filename = $1; my $output_file = $output_path . $base_filename . '.out.c'; open(my $infd, '<', $input_file) || die "$input_file: $!\n"; open(my $outfd, '>', $output_file) || die "$output_file: $!\n"; # delayed_lines accumulates comments that we might want to move to # after an inserted "break;". my $delayed_lines = ''; # True if we're handling a multiline else-if condition my $in_else_if = 0; # The accumulated line my $else_if_line; my $else_if_lineno; # True if we're expecting to suppress any braces appearing within case my $suppress_braces = 0; while (<$infd>) { chomp; if ($in_else_if) { my $rest = $_; # collapse leading whitespace $rest =~ s/^\s+//; $else_if_line .= ' ' . $rest; # Double right paren is currently sufficient to detect completion if ($else_if_line =~ m/\)\)$/) { process_else_if($else_if_line, $else_if_lineno); $in_else_if = 0; } } elsif (m/^\telse if \(.*Matches(CS)?\(/) { $else_if_line = $_; $else_if_lineno = $.; # Double right paren is currently sufficient to detect completion if ($else_if_line =~ m/\)\)$/) { process_else_if($else_if_line, $else_if_lineno); } else { $in_else_if = 1; } } elsif ($suppress_braces == 1 and m/^\t\{$/) { # don't print $suppress_braces = 2; } elsif ($suppress_braces == 2 and m/^\t\}$/) { # don't print, but do flush delayed lines if ($delayed_lines ne '') { print $outfd $delayed_lines; $delayed_lines = ''; } $suppress_braces = 0; } elsif (m/^$/ || m|^\s*/\*| || m|^\s*\*/| || m|^\s+\*|) { # delay output of blank and comment lines $delayed_lines .= $_ . "\n"; } else { if ($delayed_lines ne '') { print $outfd $delayed_lines; $delayed_lines = ''; } print $outfd "$_\n"; } } close($infd); close($outfd); sub process_else_if { my ($else_if_line, $else_if_lineno) = @_; # Strip the initial "else if (", which we know is there $else_if_line =~ s/^\telse if \(//; # Handle OR'd conditions my $isfirst = 1; while ($else_if_line =~ s/^(Head|Tail|)Matches(CS|)\((("[^"]*"|MatchAnyExcept\("[^"]*"\)|[A-Za-z,\s])+)\)\s*\|\|\s*// ) { my $typ = $1; my $cs = $2; my $args = $3; process_match($typ, $cs, $args, $isfirst); $isfirst = 0; } # Check for AND'd condition if ($else_if_line =~ s/^(Head|Tail|)Matches(CS|)\((("[^"]*"|MatchAnyExcept\("[^"]*"\)|[A-Za-z,\s])+)\)\s*&&\s*// ) { my $typ = $1; my $cs = $2; my $args = $3; process_match($typ, $cs, $args, $isfirst); $isfirst = 0; print $outfd "\t\tif ($else_if_line\n"; $suppress_braces = 0; } elsif ($else_if_line =~ s/^(Head|Tail|)Matches(CS|)\((("[^"]*"|MatchAnyExcept\("[^"]*"\)|[A-Za-z,\s])+)\)\)$// ) { my $typ = $1; my $cs = $2; my $args = $3; process_match($typ, $cs, $args, $isfirst); $isfirst = 0; $suppress_braces = 1; } else { warn "could not process if condition at line $else_if_lineno: the rest looks like $else_if_line\n"; if ($delayed_lines ne '') { print $outfd $delayed_lines; $delayed_lines = ''; } print $outfd "\telse if ($else_if_line\n"; } } sub process_match { my ($typ, $cs, $args, $needbreak) = @_; print $outfd "\t\t\tbreak;\n" if $needbreak; if ($delayed_lines ne '') { print $outfd $delayed_lines; $delayed_lines = ''; } print $outfd "\t\tcase ${typ}Matches${cs}(${args}):\n"; } sub usage { die <] input_file --output Output directory (default '.') convert_tabcomplete.pl transforms tab-complete.c to tab-complete.out.c. EOM }