goo blog サービス終了のお知らせ 

片目猫Tools

プログラマ片目猫の送る便利ツール集

Dot.exeのダミープログラムを作る

2008-01-27 22:48:55 | ツール
環境は整ったので、さっそくダミープログラムを作りましょう。

まずは、プロジェクトの新規作成です。
作成するのは、「Win32コンソールアプリケーション」
です。
そして、プロジェクト名はもちろん"dot"です。

前回書いたように、まずプロジェクトで使用する文字セットを「設定なし」
に変えます。

まず、コマンドライン引数の内容を出力するコードを書きましょう。
#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
	FILE * fp = NULL;

	fp = fopen("c:¥¥result.txt","a");

	if(fp == NULL)
	{
		printf("error!¥n");
		return 1;
	}

	for(int i=0;i<argc;i++)
	{
		fprintf(fp,"%s¥n",argv[i]);
	}

	fclose(fp);
	return 0;
}

こんな感じです。
c:¥result.txtへ結果が出力されます。
※出力先は、適当に変更してください。

このプログラムをビルドすると、dot.exeファイルが得られます。
これを、Graphvizのオリジナルのdot.exeと置き換えるのです。
オリジナルのdot.exeは、dot.exe.orgとでもリネームしておきましょう。

そして、Doxygenを実行すると... c:¥result.txtができました!
内容は、こんな感じです。
================================================================
C:¥Program Files¥Graphviz2.17¥¥bin¥dot.exe
dir_61bd93f948ed629e3277a84e9e481dc1_dep.dot
-Tpng
-o
dir_61bd93f948ed629e3277a84e9e481dc1_dep.png
================================================================

狙いどおり、dot.exeへのコマンドライン引数を得ることができました。

さらに、引数で渡された内容から、目的のdotファイルをコピーするコードを
書きます。
#include "stdafx.h"
#include "string.h"
#include "windows.h"

int _tmain(int argc, _TCHAR* argv[])
{

	FILE * fp = NULL;
	char str[256];

	strcpy(str,"c:¥¥");
	int len = strlen(str);
	strcpy(str+len,argv[1]);
	
	fp = fopen("c:¥¥result.txt","a");

	if(fp == NULL)
	{
		printf("error!¥n");
		return 1;
	}

	for(int i=0;i<argc;i++)
	{
		fprintf(fp,"%s¥n",argv[i]);
	}

	CopyFile(argv[1],str,FALSE);

	fclose(fp);
	return 0;
}

これで、c:以下にdotファイルがコピーされます。
ビルドしてできたdot.exeを、再度Graphvizのものと置き換えてDoxygenを
実行すると...

めでたくdotファイルを得ることができました!

これで説明は終わりです。
今回のコマンドライン引数を調べるプログラムは、いろいろ応用が効きます。
Graphvizに関わらず、いろいろ試してみてください。

あと、置き変えたファイルは、オリジナルのファイルに戻すのを忘れないで
くださいね!

それでは。

関数を戻り値、関数名、引数に分割する。

2008-01-23 03:46:02 | ツール
今日は、関数を分析するツールを載せます。下のコードを ana_func.pl としてEUC-JPで保存してください。
↓↓↓↓↓ここから↓↓↓↓↓
#!c:\perl\bin\perl.exe
#==============================================================================
# ana_func Copyright 2008 katameneko
#------------------------------------------------------------------------------
#【概要】
# 関数定義を解析し、戻り値型、関数名、引数型、引数名を抽出する。
#
#【使用方法】
# perl ana_func.pl ファイル名
#
#【出力】
# 戻り値:int
# 関数名:func2
# 引数型:int 引数名:i
#
#==============================================================================
#【改訂履歴】
#==============================================================================
require 'jcode.pl';

#==============================================================================
# 変数定義
#==============================================================================
$file;

#==============================================================================
# メインルーチン
#==============================================================================
# 引数チェック
if($#ARGV <0) &sjprint("ファイルが指定されていません。\n");
&sjprint("perl ana_func.pl ファイル名\n");
exit;
}
$file = $ARGV[0];

open (IN,"$file") || die &sjprint("$fileをオープンできません。\n");

# ファイル読み込み&整形
$tmp = "";
foreach $line (<IN>)
{
$line =~ s/\t/ /g; # タブ⇒スペース
$line =~ s/ +/ /g; # 連続スペースを一つに
$line =~ s/^ //g; # 先頭スペース削除

$line =~ s/\/\/.*\n/\n/gi; # //コメント削除
$line =~ s/\#.*\n/\n/g; # #以降削除。
$line =~ s/: /:/g; # :後のスペース削除
$line =~ s/\; /\;/g; # ;後のスペース削除

chomp($line); # 改行文字削除
$tmp .= $line;
}
$tmp = &del_comment($tmp);

@funcs = split(/;/,$tmp);

foreach $func (@funcs)
{
@analyze = &ana_func($func);
@arg = split(/\,/,$analyze[2]);
$arg_num = @arg;

sjprint("戻り値:$analyze[0]\n関数名:$analyze[1]\n");

if($arg_num == 0 )
{
sjprint("引数なし\n");
}
else
{
$i = 1;
foreach $a (@arg)
{
@item = split(/ /,$a);
$item_num = @item;

if ( $item_num == 0)
{
sjprint("引数なし\n");
}
elsif ( $item_num == 1)
{
if ($item[0] eq "void")
{
sjprint("引数なし\n");
}
else
{
sjprint("引数:$item[0]\n");
}
}
else
{
$v = pop(@item);
$s = join(" ",@item);
sjprint("引数型:$s、引数名:$v\n");
}
$i++;
}
}
print "\n";
}
exit; # メインルーチン終了
#==============================================================================
# 関数定義
#==============================================================================
#------------------------------------------------------------------------------
# Shift-JISで標準出力
#------------------------------------------------------------------------------
sub sjprint
{
print jcode::sjis("@_[0]");
}
#------------------------------------------------------------------------------
# c/c++コメント削除
#------------------------------------------------------------------------------
# 引数:ソースコード全体を連結した文字列。行末には改行文字(\n)が入っていること
#------------------------------------------------------------------------------
sub del_comment
{
$str = @_[0];

#--------------------------------------
# //コメント削除
$str =~ s/\/\/.*\n/\n/gi;

#--------------------------------------
# /**/コメント削除
# 改行文字変換 \n -> !@@!
$str =~ s/@@/@@@@/gi;
$str =~ s/\n/!@@!/gi;

# 改行文字追加 /**/が1行に1つになるように。
$str =~ s/\*\//\*\/\n/g;

# /**/コメント削除の実行
$str =~ s/\/\*.*\*\/\n//gim;

# 改行文字変換戻し !@@! -> \n
$str =~ s/!@@!/\n/gi;
$str =~ s/@@@@/@@/gi;

return $str;
}
#------------------------------------------------------------------------------
# 関数分析
#------------------------------------------------------------------------------
# 引数:関数定義
# (ex)int func(int i,int j);
# 戻り値:$ret[0] 戻り値、$ret[1] 関数名、$ret[2] 引数※(引数型1 引数1, 引数型2 引数2)
#------------------------------------------------------------------------------
sub ana_func
{
my $func = @_[0];
my @ret;

# 関数整形
# *を左よせにする。
$func =~ s/\*/ \* /gi;
$func =~ s/ +/ /gi;
$func =~ s/ \*/\*/gi;

#(の前後で分ける。
$func =~ /\(/;
$pre = $`;
$post = $';
$post =~ s/\)//gi;

@pre_list = split(/ /,$pre);
push(@ret,$pre_list[0]);
push(@ret,$pre_list[1]);
push(@ret,$post);

return @ret;
}
__END__
#==============================================================================
# 以降コメント。
#==============================================================================
↑↑↑↑↑ここまで ↑↑↑↑↑サンプルのソースを次の投稿に載せます。

setting.pl

2008-01-21 21:20:25 | ツール
#==============================================================================
#  setting                                       Copyright 2008 katameneko
#------------------------------------------------------------------------------
#【概要】
# class_sceleton.pl用設定ファイル。
#
#【カスタマイズ】
# ・関数のキーワード(virtual,staticなど)の追加・変更。
#   ⇒%keyword_hashの内容を変更する。
# ・戻り値の型に対し、スケルトンで返却する値の追加、変更。
#   ⇒%ret_type_hashの内容を変更する。
#
#==============================================================================	
#==============================================================================
# 定数定義
#==============================================================================

# 関数の先頭にありうるキーワード
# 0:スケルトンに出力しない。
# 1:スケルトンに出力する。
%keyword_hash = (
				 "virtual"	=> 0,
				 "static"	=> 0,
				 "const"	=> 1
				);

# 戻り値の型に対するreturn値の設定
# %ret_type_hashへ"型"=>"対応する返却値"の形式で追加する。
%ret_type_hash = (
				  "bool"	=> "TRUE",
				  "boolean"	=> "TRUE",
				  "char"	=> "0",
				  "byte"	=> "0",
				  "word"	=> "0",
				  "dword"	=> "0",
				  "short"	=> "0",
				  "int"		=> "0",
				  "long"	=> "0"
				 );


class_sceleton.pl

2008-01-21 21:18:15 | ツール
#!c:\perl\bin\perl.exe
#==============================================================================
#  class_sceleton                              Copyright 2008 katameneko
#------------------------------------------------------------------------------
#【概要】
# クラス定義からスケルトンを生成する。
#
#【使用方法】
# perl class_sceleton.pl ヘッダファイル名
#
#【出力】
# ヘッダファイルに記載されている関数の解析情報およびスケルトン
#
#【カスタマイズ】
# setting.plでの定数定義を変更することで、下記項目を変更可能。
# ・関数のキーワード(virtual,staticなど)の追加・変更。
# ・戻り値の型に対し、スケルトンで返却する値。
#
#【制約事項】
# ・ヘッダファイルのはじめに存在する1クラスしか処理できない。
# ・マクロ(#define,#include,#if,#ifdef...)には対応していない。
# ・\での行連結に対応していない。
#==============================================================================
#【改訂履歴】
#==============================================================================
require 'jcode.pl';
require 'setting.pl';	# 定数定義

#==============================================================================
# 変数定義
#==============================================================================
$header = $ARGV[0];		# ヘッダファイル名
@honbun;				# ファイルの内容
$class_name = "";		# クラス名
@func_def;				# クラス内の関数定義
$nest_num = 0;			# {}のネスト数
$state = "out";			# 状態(class外、クラス内探し中、クラス内)
@class_sceleton_list;	# 全クラスのスケルトン情報
$debug_info = "";		# デバッグ用情報。

#==============================================================================
# メインルーチン
#==============================================================================
# 引数チェック
if ( $header eq undef )
{
	&sjprint("ヘッダファイルが指定されていません。\n");
	&sjprint("perl class_sceleton.pl ヘッダファイル名\n");
	exit;
}

open (IN,"$header") || die &sjprint("$headerをオープンできません。\n");

# ファイル読み込み&整形
$tmp = "";
foreach $line (<IN>)
{
	$line =~ s/\t/ /g;		# タブ⇒スペース
	$line =~ s/ +/ /g;		# 連続スペースを一つに
	$line =~ s/^ //g;		# 先頭スペース削除

	$line =~ s/\/\/.*\n/\n/gi;	# //コメント削除 
	$line =~ s/\#.*\n/\n/g;	# #以降削除。
	$line =~ s/: /:/g;		# :後のスペース削除
	$line =~ s/\; /\;/g;	# ;後のスペース削除

	chomp($line);	# 改行文字削除
	$tmp .= $line;
}
$tmp = &del_comment($tmp);

# 処理しやすいように再度改行文字を入れる。
$tmp =~ s/\;/\;\n/g;
$tmp =~ s/\:/\:\n/g;
$tmp =~ s/\{/\n\{\n/gi;
$tmp =~ s/\}/\n\}\n/gi;
$tmp =~ s/^class /\nclass /g;

# sjprint ($tmp);	#デバッグ:成形後のヘッダファイル出力。

@honbun = split(/\n/,$tmp);
#-----------------------------------------------------------------------------
# ファイル情報を状態によって処理する。
#-----------------------------------------------------------------------------
# out	:クラス外
# search:classキーワード後、{に到達するまで
# in	:クラス内
#-----------------------------------------------------------------------------
foreach $str (@honbun)
{
	if ($state eq "out")
	{
		# クラス外の場合
		# クラス名を取得する。
		if ( ( $str =~ /class/) & ($str !~ /\;/ ) )
		{
			@list = split(/ /,$str);
			$cnt = @list;
			for($i=0;$i<$cnt;$i++)
			{
				if ($list[$i] eq "class")
				{
					$class_name = $list[$i+1];
					$debug_info .= "$class_name";
					last;
				}
			}
			$state = "search";
		}
	}
	elsif ($state eq "search")
	{
		# クラス内探し中の場合
		# '{'が見つかったらクラス内と判定。
		if ($str =~ /{/)
		{
			$nest_num++;
			$state = "in";
		}
	}
	elsif ($state eq "in")
	{
		# クラス内の場合
		# '{'が見つかったらネストを加算。
		if ($str =~ //)
		{
			$nest_num--;
			if ($nest_num == 0)
			{
				$state = "out";
				push( @class_sceleton_list,&make_sceleton(($class_name,@func_def)) );
				
				# 変数初期化
				undef(@func_def);
				next;
			}
		}
		# ネストが1の場合のみ、関数を探す。
		if ($nest_num == 1)
		{
			# ()が存在すれば、関数と判定。
			if( ($str =~ /\(/) & ($str =~ /\)/) )
			{
				push(@func_def,$str);
			}
		}
	}
}
close (IN);

#-------------------------
# 結果表示   
#-------------------------

if ($class_name ne "")
{
	print "\#include \"$header\"\n\n";
	foreach(@class_sceleton_list)
	{
		sjprint ($_);
		print "\n\n";
	}
}
else
{
	&sjprint("クラスは見つかりませんでした。\n");
}


# デバッグ情報表示
#&sjprint ($debug_info);

exit;	# メインルーチン終了
#==============================================================================
# 関数定義
#==============================================================================
#------------------------------------------------------------------------------
# Shift-JISで標準出力   
#------------------------------------------------------------------------------
sub sjprint
{
	print jcode::sjis("@_[0]");
}
#------------------------------------------------------------------------------
# c/c++コメント削除
#------------------------------------------------------------------------------
# 引数:ソースコード全体を連結した文字列。行末には改行文字(\n)が入っていること
#------------------------------------------------------------------------------
sub del_comment
{
	$str = @_[0];

	#--------------------------------------
	# //コメント削除 
	$str =~ s/\/\/.*\n/\n/gi;

	#--------------------------------------
	# /**/コメント削除 
	# 改行文字変換 \n -> !@@!
	$str =~ s/@@/@@@@/gi;
	$str =~ s/\n/!@@!/gi;

	# 改行文字追加 /**/が1行に1つになるように。
	$str =~ s/\*\//\*\/\n/g;
	
	# /**/コメント削除の実行
	$str =~ s/\/\*.*\*\/\n//gim; 

	# 改行文字変換戻し !@@! -> \n
	$str =~ s/!@@!/\n/gi;
	$str =~ s/@@@@/@@/gi;

	return $str;
}
#------------------------------------------------------------------------------
# スケルトン生成
#------------------------------------------------------------------------------
# 引数		:$クラス名,@関数定義リスト
# Global変数:%keyword_hash キーワードのハッシュ
#			  %ret_type_hash 戻り値の型のハッシュ
#------------------------------------------------------------------------------
sub make_sceleton()
{
	#引数取得
	my($class_name,@func_def) = @_;

	# 変数定義
	# クラススケルトン情報
	my($class_sceleton);
	
	# デバッグ用情報。
	my($debug_info);
	
	foreach(@func_def)
	{
		# 変数定義
		my(@keyword);
		my($ret_type);
		my($func_name);
		my($arg_list);
		
		# *,&の整形処理。型にくっつけ、関数or引数との間を空ける。
		$_ =~ s/\*/\* /gi;			# *の後ろにスペースを入れる。
		$_ =~ s/\&/\& /gi;			# &の後ろにスペースを入れる。
		$_ =~ s/ +/ /gi;			# 連続スペースを1つに。
		$_ =~ s/ \*/\*/gi;			# *の前のスペースは消す。
		$_ =~ s/ \&/\&/gi;			# &の前のスペースは消す。
		
		$_ =~ /\(/;
		$pre  = $`;
		$post = $';

		$post =~ s/\).*//;	# ()のネストを考慮していない。問題ない?

		# (より前を処理する。
		@pre_list = split(/ /,$pre);
		$num = @pre_list;
		
		# キーワードチェック
		while ( $keyword_hash{ $pre_list[0] } ne undef )
		{
			$key = shift( @pre_list );
			if ( $keyword_hash{ $key } == 1 )
			{
				push( @keyword, "$key " );
			}
			$num--;
		}
		
		#コンストラクタ/デストラクタチェック
		if ($num == 1)
		{
			if ( $pre_list[0] eq $class_name )
			{
				$debug_info .= "コンストラクタ\n";
				$func_name = "$class_name";
			}
			elsif( $pre_list[0] eq "~$class_name" )
			{
				$debug_info .= "デストラクタ\n";
				$func_name = "~$class_name";
			}
			else
			{
				die &sjprint("!エラー:不正な関数名が指定されました。: $pre_list[0]\n");
			}
		}
		elsif ($num == 2)
		{
			$debug_info .= "戻り値の型:$pre_list[0]\n";
			$debug_info .= "関数名:$pre_list[1]\n";
			$ret_type = "$pre_list[0] ";
			$func_name = $pre_list[1];
		}
		else
		{
			die &sjprint("!エラー:関数定義が不正です。: 。@pre_list\n");
		}

		# (より後を処理する。
		@func_args = split(/\,/,$post);

		$func_arg_num = 0;
		foreach $func_arg(@func_args)
		{
			$func_arg_num++;
			$debug_info .= "引数$func_arg_num:$func_arg\n";
		}

		#--------------------------------------------------------------------------
		# スケルトン生成
		#--------------------------------------------------------------------------
		$arg_list = "";
		foreach $func_arg(@func_args)
		{
			$func_arg =~ s/=.+//gi;
			$arg_list .= "$func_arg,";
		}
		chop($arg_list);

		$class_sceleton .= "@keyword$ret_type$class_name\:\:$func_name\($arg_list\)\n\{\n\t//## begin $class_name\:\:$func_name\n";

		$ret_type =~ s/ //gi;

		#--------------------------------------------------------------------------
		# 戻り値の出力。
		#--------------------------------------------------------------------------
		# コンストラクタ、デストラクタ、void型 ⇒ 戻り値なし。
		# ポインタ ⇒ return NULL;
		# 登録されている型 ⇒ 対応する値
		# 参照戻り値 ⇒ ポインタ(dummy)宣言、return *dummy。※Warnningとなる。
		# その他 ⇒ 戻り値の型の変数(dummy)宣言、return dummy;
		#--------------------------------------------------------------------------
		if( ( $func_name ne "$class_name" ) & 
			( $func_name ne "~$class_name" ) & 
			( $ret_type ne "void" ) )
		{
			if($ret_type =~ /\*/ )
			{
				$class_sceleton .=  "\n\treturn NULL\;";
			}
			else
			{
				if( $ret_type_hash{ $ret_type } ne undef)
				{
					$class_sceleton .= "\n\treturn $ret_type_hash{ $ret_type }\;";
				}
				else
				{
					if ( $ret_type =~ /\&/ )
					{
						$ret_type_ = $ret_type =~ s/\&//gi;
						$class_sceleton .=  "\n\t$ret_type* dummy\;\n\n\treturn *dummy\; "
					}
					else
					{
						$class_sceleton .=  "\n\t$ret_type dummy\;\n\n\treturn dummy\; "
					}
				}
			}
		}
		$class_sceleton .=  "\n\n\t//## end $class_name\:\:$func_name\n}\n";
	}
	# &sjprint($debug_info);		# デバッグ情報表示。
	return $class_sceleton;
}
__END__
#==============================================================================
# 以降コメント。
#==============================================================================

クラスヘッダファイルからスケルトンを作れ!

2008-01-21 21:01:46 | ツール
巷では、やれクラス図からソースコード生成だ!はたまたソースコードから逆にクラス図をリバースエンジニアリングだ!
などとUMLモデリングツールがもてはやされていますが、そうしたツールは大概お高いのです。
片目猫はしょぼい会社で働いているので、とても買ってもらえないのです。

買ってもらえなければ、あきらめるのか?
そんなことではプログラマとは言えない。無ければ作ってしまえばいいのです!

そんな大層なことができるのか?
うーん。どうだろう。

モデリングツールなんてGUI作ってる時間ありません。
そもそも、そんな高機能必要だろうか?

クラス関連図であれば、クラスメンバ詳細なんてなくて、クラス間の関係が図示できればいいのだから、ExcelやVisioで十分だし。

クラス定義をテキストでじゃかじゃか書いて、doxygen&Graphvizを使ってクラス図や継承関係を図に起こすことだってできるわけだし。

じゃあ、クラス定義からスケルトンを吐き出すツールがあればいいんじゃないか。
これならそんなに難しくなく作れそうだし、必ずヘッダファイルからスケルトンを生成すれば、ソースコードとの整合性を気にする必要もなくなるし。いいかも。
言語仕様にしても、完璧に満たしてなくとも、とりあえず自分が使う範囲の仕様をサポートしていれば、十分じゃないか?

こんな感じのツールができたらけっこう役に立ちそうだ。
・クラス定義(ヘッダファイル)からクラスのスケルトンを生成できる。
・言語仕様は、とりあえず自分が作る範囲のもので作れればOK。
 サポートしていない定義が出てきたらそのつどツールをバージョンアップする。

そんなわけで、片目猫Tools記念すべき第1弾は、クラスのヘッダファイルの定義から、スケルトンを生成するツールです。

【使い方】
次以降の投稿内容をタイトルをファイル名として保存してください。
(注意:class_sceleton.plとsetting.plはEUC-JP、sample.hはShift-JISで保存します。)
class_sceleton.plとsetting.pl、sample.hを同じフォルダに入れましょう。
あと、jcode.plは下記アドレスからダウンロードしてきてください。ftp://ftp.iij.ad.jp/pub/IIJ/dist/utashiro/perl/

Perlが実行できる環境であれば、
>perl class_sceleton.pl sample.h > sample.cpp
と実行すると、sample.hに定義されているクラスのスケルトンが生成できます。

このスクリプトの良いところは、スケルトンにreturn値を自動設定してくれるところです。

一般的なモデリングツールで自動生成したスケルトンには、return処理が書かれていないため、コンパイルが通りません。
class_sceleton.plは、一般的な型ならデフォルトでreturn値を設定してますし、ポインタであればNULLを返します。未知の型でも、どうにかコンパイルエラーは起こらないコードを吐き出します。

後は、デフォルトコンストラクタの存在しない親クラスを持つクラスのコンストラクタに対応できれば良いのだけど。こればかりは親クラスを探し出して、コンストラクタの引数を調べて。。。ということをしなくてはいけないので、さすがに難しい。ここらで良しとしましょう。

手元にあるヘッダファイルで試してみてください。

このスクリプトは作成に丸1日かかってしまいました。
使う機会が多ければいいなあ。