protoize.pl

最近、事情があって古い C のソースを利用することがあったのだが、関数定義が K&R 式(宣言部は #ifdef で分岐)。
定義に関しては、今の C コンパイラでも K&R 式で通るから K&R 式で統一するというやり方だろう。
しかし、これでは C++ コンパイラは通らない。
困った。

それで、K&R 式関数定義を ANSI 式に変換するプログラムはないかと探したら、あった。
protoize。

とりあえず、cygwin で動かしてみる。
gcc といっしょに(エイリアスのようなものらしい)入っている。
実行。

すると、パスがらみのバグが出る。

protoize: compiling `/cygdrive/c/temp.c'
protoize: /c/usr/include/sys/lock.h: 状態を取得できません: No such file or directory
protoize: /c/usr/include/sys/reent.h: 状態を取得できません: No such file or directory
protoize: /c/usr/include/stdio.h: 状態を取得できません: No such file or directory
protoize: /c/cygdrive/c/temp.c: 状態を取得できません: No such file or directory
protoize: `/c/cygdrive/c/temp.c' not converted

最初にドライブレターがついてしまっている。
この問題らしい。

http://www.rubyist.net/~nobu/t/tb.rb/20050831

2005年か。
まあ、cygwin で protoize を使おうなんていうのは例外的な状況だからなぁ。

事情があって、cygwin 以外の Unix 系の環境はその時使えなかった。
困った。

大した処理じゃないだろう…と思って、ちょっと Perl で書いてみた。

#protoize.pl
use strict;
my @keywords =qw(
	auto break case char const continue default do
	double else enum extern float for goto if
	int long register return short signed sizeof static
	struct switch typedef union unsigned void volatile while
);
my %keyword_map;
@keyword_map{@keywords} = ();

my $content;
my $sp = qr{(?:\s|/\*.+?\*/)*};
my $sp_plus = qr{(?:\s|/\*.+?\*/)+};
my $id = qr{[A-Za-z_][0-9A-Za-z_]*};

{ local $/; $content = <STDIN>; }
$content =~ s{($id)($sp\()((?:$sp$id$sp,)*$sp$id$sp)(\))((?:$sp(?:$id$sp_plus)*(?:\**$sp$id$sp,$sp)*\**$sp$id$sp;)*)($sp)(?=\{)}
{
	my $whole_str = $&;
	my $name = $1;
	my $pre_paren = $2;
	my $arg_str = $3;
	my $post_paren = $4;
	my $proto_str = $5;
	my $pre_bracket = $6;
	if ($proto_str !~ /;/ or exists $keyword_map{$name}) {
		$whole_str;
	} else {
		my %id_map = ();
		while ($proto_str =~ m{$sp((?:$id$sp_plus)*)((?:\**$sp($id)$sp,$sp)*\**$sp$id$sp);}sgo) {
			my $type = $1;
			my $id_str = $2;
			my @id_array = split /,/, $id_str;
			for my $one_id(@id_array) {
				$one_id =~ m{^$sp(\**$sp($id))$sp$};
				$id_map{$2} = $type.$1;
			}
		}
		my @arg_array = split/,/, $arg_str;
		my @result_array = ();
		for my $arg(@arg_array) {
			$arg =~ m{^($sp)($id)($sp)$};
			die $name if not exists $id_map{$2};
			push @result_array, $1.$id_map{$2}.$3;
		}
		$name.$pre_paren.join(',', @result_array).$post_paren.$pre_bracket;
	}
}sgoe;
print $content;

上のソースを protoize.pl として保存して、

perl protoize.pl < file.c > file_ansi.c

のようにして実行。

最初はすごく遅かったけど、qr や o オプションを使ったらそれなりに速くなった。
しかし、コメント処理はやはり重いはずなので、時間がかかる時は

my $sp = qr{\s*};
my $sp_plus = qr{\s+};

とでもしたほうがいいかも。

宣言部に関しては unifdef 任せ。
だから、(互換性確保のために K&R にしているわけではない)本当に古いソースの場合はこれだけでは不十分。