Vim is incredibly powerful, but it has the downside of a steep learning curve. Once that is surmounted, however, it’s easy to do a lot of powerful tricks with it. Many of these are things that one sees in full-blown IDEs. My setup provides auto-completion, test suite management, build management, source control integration and a variety of other useful tricks. One of the things that I really appreciate about vim is its handy filtering ability, but most vim developers don’t seem to be aware of it. I’ll explain one way of using it, with a primitive java2perl filter.

Java, as you’ve probably noticed, is one of the most popular programming languages out there. Thus, if you are looking for a particular algorithm, there’s a good chance of a Java solution on the Web. However, manually transforming this into your language of choice can be tedious. Enter vim filters.

Save the following program somewhere in your path as reverse.pl and make it executable (chmod +x reverse.pl).

#!/usr/bin/perl 

use strict;
use warnings;

my $string = do { local $/; <STDIN> }; # slurp STDIN
print scalar reverse $string;

A vim filter provides its data via STDIN and reads STDOUT and STDERR. Add the following line to your .vimrc:

vnoremap ,rv :!reverse.pl <cr>  " only work in 'visual' mode

The ‘v’ in the mapping means the mapping only works in ‘visual’ mode. Now if you select a range of lines (try “{shift}V and use navigation keys to select a range) and type ‘,rv’, the entire text selection will be reversed (useless, I know, but it shows how a filter works).

In order for us to get a proper java2perl filter, we need to consider a few things. Since both Java and Perl are considered “Algol-style languages”, a straight-forward syntactic transformation is not terribly difficult. However, semantic transformation is tricky. For a simple example, the code to read a file’s contents in Java and Perl is radically different. As a result, while I had considered a full-blown lexer/parser solution, I realized that it would take so long and have so many edge cases that it wasn’t worth the trouble since I merely wanted to use the occasional algorithm written in Java. As many standard libraries in Java and Perl have no direct translations, I felt that a simplistic solution was fine and I could manually fix what I needed. Here’s the code I threw together:

#!/usr/bin/perl 

use strict;
use warnings;

my $method = do { local $/; <STDIN> };

my $class = qr/[[:upper:]][[:word:]]*/;    # by convention
my $type  = join
  '|' => qw/int char float byte short long double boolean/,
  $class;

my $var_name      = qr/\b[[:word:]]+\b/;
my $declaration   = qr/(?:$type(?:\[\])?)\s+$var_name/;
my $c_declaration = qr/($type(?:\[\])?)\s+($var_name)/;   # capturing
my $var_list      = qr/$declaration\s*(?:,\s*$declaration)*/;
my $return        = "$type|void";
my $access_level  = qr/public|private|protected/;
my $method_name   = qr/[[:word:]]+/;

my $signature = qr/^
        \s*
        $access_level
        \s+ 
        (?:static\s+)?
        (\w+)               # return type
        \s+ 
        ($method_name)
        \s* 
        \(
        \s* 
        ($var_list)
        \s* 
        \)
        \s* 
        {            # the opening brace of the method
/mx;

my %seen_vars;

if ( $method =~ /$signature(.*)}\s*/s ) {
    
    my ( $return_type, $method_name, $arguments, $method_body ) =
      ( $1, $2, $3, $4 );
    my $method_start = "sub $method_name {\n";
    my $method_end   = 'void' eq $return_type ? "    return;\n}" : "}";
    my $args = "    my ( ";
    my @vars;
    while ( $arguments =~ /$c_declaration/g ) {
        push @vars => "\$$2"; 
        $seen_vars{$2} = 1;
    }
    $args .= join ', ' => @vars;
    $args .= ' ) = @_;';
    $method_start .= "$args\n";
    $method_body = munge_body($method_body);
    print "$method_start$method_body$method_end";
}

sub munge_body {
    my $body = shift;

    # convert comments
    $body =~ s{(\s*)//}{$1#}mg;

    my @declarations;

    # find all variable declarations
    while ( $body =~ /($c_declaration)/g ) {
        push @declarations => $1;
        my $var = $3;
        $seen_vars{$var} = 1;
    }

    # make the variable declarations
    foreach my $declaration (@declarations) {
        $declaration =~ /$c_declaration/;
        my $var = $2;
        $body =~ s/$declaration/my \$$var/g;
    }

    # just in case ...
    delete $seen_vars{$_} foreach qw/ my local our /;

    # prefix variables with '$'
    foreach my $var (keys %seen_vars) {
        $body =~ s/(?<!\$)$var\b/\$$var/g;
    }
 
    return $body;
}

It’s not pretty, but hey, this is Perl :)

Then I added my filter to my .vimrc.

vnoremap ,rw :!java2perl.pl <cr>  " only work in 'visual' mode

The way this works is that I have to select the first line (the signature) of the method in visual mode and then navigate down to the end of the method. Since I’m working with line-drawing algorithms right now, I decided to try the Bresenham alorithm. Here’s the Java:

public void lineBresenham(int x0, int y0, int x1, int y1, Color color)
{
    int pix = color.getRGB();
    int dy = y1 - y0;
    int dx = x1 - x0;
    int stepx, stepy;

    if (dy < 0) { dy = -dy;  stepy = -1; } else { stepy = 1; }
    if (dx < 0) { dx = -dx;  stepx = -1; } else { stepx = 1; }
    dy <<= 1;                                                  // dy is now 2*dy
    dx <<= 1;                                                  // dx is now 2*dx

    raster.setPixel(pix, x0, y0);
    if (dx > dy) {
        int fraction = dy - (dx >> 1);                         // same as 2*dy - dx
        while (x0 != x1) {
            if (fraction >= 0) {
                y0 += stepy;
                fraction -= dx;                                // same as fraction -= 2*dx
            }
            x0 += stepx;
            fraction += dy;                                    // same as fraction -= 2*dy
            raster.setPixel(pix, x0, y0);
        }
    } else {
        int fraction = dx - (dy >> 1);
        while (y0 != y1) {
            if (fraction >= 0) {
                x0 += stepx;
                fraction -= dy;
            }
            y0 += stepy;
            fraction += dx;
            raster.setPixel(pix, x0, y0);
        }
    }
}

By selecting that in visual mode and applying my filter, I get this:

sub lineBresenham {
    my ( $x0, $y0, $x1, $y1, $color ) = @_;

    my $pix = $color.getRGB();
    my $dy = $y1 - $y0;
    my $dx = $x1 - $x0;
    my $stepx, stepy;

    if ($dy < 0) { $dy = -$dy;  stepy = -1; } else { stepy = 1; }
    if ($dx < 0) { $dx = -$dx;  $stepx = -1; } else { $stepx = 1; }
    $dy <<= 1;                                                  # $dy is now 2*$dy
    $dx <<= 1;                                                  # $dx is now 2*$dx

    raster.setPixel($pix, $x0, $y0);
    if ($dx > $dy) {
        my $fraction = $dy - ($dx >> 1);                         # same as 2*$dy - $dx
        while ($x0 != $x1) {
            if ($fraction >= 0) {
                $y0 += stepy;
                $fraction -= $dx;                                # same as $fraction -= 2*$dx
            }
            $x0 += $stepx;
            $fraction += $dy;                                    # same as $fraction -= 2*$dy
            raster.setPixel($pix, $x0, $y0);
        }
    } else {
        my $fraction = $dx - ($dy >> 1);
        while ($y0 != $y1) { 
            if ($fraction >= 0) {
                $x0 += $stepx;
                $fraction -= $dy;
            }
            $y0 += stepy;
            $fraction += $dx;
            raster.setPixel($pix, $x0, $y0);
        }
    }
    return;
}

That isn’t valid Perl, but only a couple of quick changes and I now have a working line drawing algorithm.

Naturally there’s lots more I could do to improve this feature, but my major need was to remove the bulk of the grunt work involved in transforming Java to Perl and this filter succeeded wonderfully.