改良Flymake for OCaml

問題

Eclipse などの統合開発環境には構文エラーがあると、その場でエラー箇所やメッセージを表示してくれる機能がある。
Emacs で同じ機能を実現するには、Flymake を使用するのが一番手っ取り早い。
詳しくは EmacsWiki の [Flymake の項を参考に。

しかし Flymake は列番号の出力に対応していないので、OCaml のような関数型言語で型エラーが発生すると、どの部分でエラーが起きているのか非常にわかりにくい。

対策

そこで、Flymake OCaml を改良して列番号をメッセージとして表示するようにしてみた。
すると、エラーと表示メッセージは次のようになる。

  • エラー

  • メッセージ

変更した ocaml_flycheck.pl は次の通り。

#!/usr/bin/env perl
# ocaml_flycheck.pl

use strict;
use warnings;

### Please rewrite the following 2 variables 
### ($ocamlc, @ocamlc_options)

my $ocamlc = 'ocamlc';          # where is ocamlc
my @ocamlc_options  = ('-c -thread unix.cma threads.cma graphics.cma'); # e.g. ('-fglasgow-exts');
my @ocamlc_packages = ();

### the following should not been edited ###

use File::Temp qw /tempfile tempdir/;
File::Temp->safe_level( File::Temp::HIGH );

my ($source, $base_dir) = @ARGV;

my @command = ($ocamlc);

while (@ocamlc_options) {
  push(@command, shift @ocamlc_options);
}

push (@command,    $source);

while (@ocamlc_packages) {
  push(@command, '-package');
  push(@command, shift @ocamlc_packages);
}

my $dir = tempdir( CLEANUP => 1 );
my ($fh, $filename) = tempfile( DIR => $dir );

system("@command >$filename 2>&1");

open(MESSAGE, $filename);
my $column = "";
while (<MESSAGE>) {
  # example message  {File "robocupenv.ml", line 133, characters 6-10:
  if (/^File "(\S+\.ml[yilp]?)", line (\d+), characters (\d+)-(\d+):\s?(.*)/) {
    print $column;
    my $error = (<MESSAGE>);       # get the next line
    chomp $error;
    print "\n"; 
    print "$1:$2:$3:";
    $column = " [$3-$4]";
    if ($error =~ /Warning(.*)/) {
	    print "$error";
    } else {
	    print "$error ";
    }
    next;
  }
  if (/\s+(.*)/) {
    my $rest = $1;
    chomp $rest;
    print $rest;
    print " ";
    next;
  }
}

close($fh);
print "$column\n";

ちなみに列番号の出力を最後にしてるのは、Flymake の仕様上

  1. 列番号の出力フォーマットが用意されていない
  2. メッセージの先頭が Warning であれば警告、そうでなければエラーと判別するためメッセージの先頭部分は変更できない

という理由による。