blockdiagを使って, コールフローを作成する

http://blockdiag.com/blockdiag-ja/build/html/index.html

を見て、なにかできないだろうかって考えてみて、cflowの結果から
コールフローが作れるなって思って、コードを書いてみた。

準備

cflow, blockdiagをインストールします

  % sudo aptitude install cflow # Ubuntu, debian
  % sudo brew install cflow     # Mac OSX
  % sudo easy_install blockdiag

コード

blockdiagのことを考えると Pythonの方がって思ったけど、
すぐには書けなかったので Perlで。

#!/usr/bin/env perl
use strict;
use warnings;

package App::cflow2blockdiag;
use Carp ();
use File::Which ();

sub new {
    my ($class, $files_ref, $ignores_regexp) = @_;

    Carp::croak("Please install cflow\n") unless File::Which::which('cflow');

    bless { files => $files_ref, ignore => $ignores_regexp }, $class;
}

sub run {
    my $self = shift;

    my $cflow_ref = $self->_exec_cflow;
    $self->cflow_to_blockdiag($cflow_ref);
}

sub cflow_to_blockdiag {
    my ($self, $cflow_ref) = @_;

    my (@flows, @current_flow);
    my $prev_indent = -1;

    open my $fh, "<", $cflow_ref or Carp::croak("Can't open strref\n");
    while ( my $line = <$fh> ) {
        chomp $line;

        my $indent_num = $line =~ s{\s{4}}{}gxms;
        $indent_num ||= 0;

        my ($func) = $line =~ m{^(\w+)\(\)};
        next if $func =~ $self->{ignore};

        if ($indent_num <= $prev_indent) {
            push @flows, join ' -> ', @current_flow;
            pop  @current_flow for 0..($prev_indent - $indent_num);
        }

        push @current_flow, $func;
        $prev_indent = $indent_num;
    }
    close $fh;

    my $flow = join ";\n\t", @flows;
    print <<__DIAG__
diagram {
  fontsize = 20;
  default_shape = roundedbox;
\t$flow
}
__DIAG__
}

sub _exec_cflow {
    my $self = shift;

    my @cmd = ('cflow', '--omit-arguments', @{$self->{files}});
    open my $cflow, "-|", @cmd or Carp::croak("Can't exec cflow\n");
    my $output = do { local $/; <$cflow>; };
    close $cflow;

    return \$output;
}

package main;
use Regexp::Assemble;

my $ra = Regexp::Assemble->new;
$ra->add('printf$');
$ra->add('^[mc]alloc$', 'free');
$ra->add('^strn?(?:cmp|cpy)$');
$ra->add('^mem(?:set|cpy)$');

unless (caller) {
    my $app = App::cflow2blockdiag->new(\@ARGV, $ra->re);
    $app->run;
}

実行

コールフローを生成したい, Cファイルを指定します

  % perl cflow2blockdiag.pl hoge.c

結果

NetBSD 5.1の bin/mkdir/mkdir.cで試してみると以下のようになります

  % perl cflow2blockdiag.pl mkdir.c
  diagram {
    fontsize = 20;
    default_shape = roundedbox;
    main -> setprogname;
    main -> setlocale;
    main -> umask;
    main -> getopt;
    main -> setmode;
    main -> err;
    main -> getmode;
    main -> usage -> getprogname;
    main -> usage -> exit;
    main -> strrchr;
    main -> mkpath -> strspn;
    main -> mkpath -> strcspn;
    main -> mkpath -> mkdir;
    main -> mkpath -> stat;
    main -> mkpath -> warn;
    main -> mkpath -> S_ISDIR;
    main -> mkpath -> chmod;
    main -> mkdir;
    main -> warn;
    main -> chmod
   }

この出力をリダイレクトし、blockdiagに与えるとコールフローを図として
得ることができます。

  % perl cflow2blockdiag.pl mkdir.c > mkdir.diag
  % blockdiag mkdir.diag

以下のような画像が得られました.

送信者 blog

まとめ

cflowの出力から, blockdiag形式で出力できるようにするための
変換プログラムを紹介しました。コールフローであれば専用のツールが
多数あるだろうから、別に blockdiagを使う必要はないかなと思いますけど、
いろいろ応用できるものはあるんじゃないかなと思いました。