#!/usr/bin/perl -w
use strict;
use warnings;

# 関数連番。複数subに対応するため。
my $funcnum=0;

# フローチャートの要素データ（参照）リスト
# 要素内容は以下（将来的にはClass::Struct化したい）
# ID => {
# 	ID       => ユニークな識別子（コマンド_行番号）,
# 	command  => コマンド種別,
# 	args     => コマンド引数,
# 	ref      => 参照元ID等のリスト（参照）@refIDs,
# 	level    => 深さ,
# }
my %nodelist;

# @nodelist->{ref}用の参照元データ（参照）リスト
# 要素内容は以下（将来的にはClass::Struct化したい）
# [ID,args]
my @refIDs;
# endswitchでの合流用にbreakを保持する、参照元データ（参照）リスト
# 要素内容は@refIDsと同じ
my @breakstack;
# switch->caseの参照情報を保持する、参照元データ（参照）リスト
# switchが深くなるほど、要素が増えていく。最終要素は直近のswitch
# 要素内容は@refIDsと同じ
my @switchstack;
# endsubでの合流用にbreakを保持する、参照元データ（参照）リスト
# 要素内容は@refIDsと同じ
my @returnlist;

# ラベルへの参照元リスト
# ラベル名 => { ID=>ID, ref=>[@refIDsと同じ] }
my %labellist;


print "digraph {\n\n";
while (<>) {
	my($command,$args) = /^\s*(\w+)\s*(.*?)\s*$/;
	next if not $command;
	SWITCH_COMMAND: {
		$_ = $command;
		my $currentID = "${command}_$.";
		
		# コメント行はスキップ
		next if /^#/;
		
		# case break（非フローチャート要素）
		/^break$/ && do {
			push @breakstack, $refIDs[-1];
			last;
		};
		# case endswitch（非フローチャート要素）
		/^endswitch$/ && do {
			@refIDs=@breakstack;
			pop @switchstack;
			last;
		};
		
		# case sub
		/^sub$/ && do {
			$funcnum++;
			@returnlist = ();
			@switchstack = ();
			@refIDs = ();
			%nodelist = ();
			$command = "start_end";
			print qq/subgraph cluster_$funcnum { label="$args";\n/;
		};
		# case endsub
		/^endsub$/ && do {
			$args    = "END";
			$command = "start_end";
			# return->endsub
			push @refIDs, @returnlist;
			# goto->label
			foreach my $label (keys %labellist) {
				if (not exists $labellist{$label}{ID}) {
					warn "ERROR: not defined label(${label})\n";
				} else {
					if (exists $labellist{$label}{ref}) {
						push @{$nodelist{$labellist{$label}{ID}}{ref}}, @{$labellist{$label}{ref}};
					}
				}
			}
		};
		# case switch
		/^switch$/ && do {
			@breakstack = ();
			push @switchstack, $currentID;
		};
		# case label
		/^label$/ && do {
			$labellist{$args}{ID} = $currentID;
		};
		# calse goto
		/^goto$/ && do {
			# ラベルへの参照元リストへ追加
			# 現状では、参照情報に引数は設定しない（将来的にはコメント的なテキストを設定したい）
			if (not exists $labellist{$args}{ref}) {
				$labellist{$args}{ref} ||= [];
			}
			push @{$labellist{$args}{ref}}, [$currentID, ""];
		};
		# case return
		/^return$/ && do {
			push @returnlist, [$currentID, $args];
			$command = "goto";
		};
		# case case
		/^case$/ && do {
			@refIDs=([$switchstack[-1], $args]);
			$args    = $currentID;
			$command = "label";
		};
		
		# default（全フローチャート要素）
		do {
			$nodelist{$currentID} = {
			    ID       => $currentID,
			    command  => $command,
			    args     => $args,
			    ref      => [@refIDs],
			    level    => scalar @switchstack,
			    };
			@refIDs=([$currentID, ""]);
		};
		# case endsub
		/^endsub$/ && do {
			print @{nodedump(\%nodelist)};
			print "}\n\n";
		};
	}
}

print "}\n";



sub nodedump {
	my $nodelist = shift;
	my @ret;
	
	# default node
	push @ret,
	    qq/# default\n/,
	    qq/edge[labeldistance=1.5,tailport=s,headport=n];\n/,
	    qq/node[height=0.2, width=1];\n/;
	push @ret, "\n";
	
	# switch
	push @ret,
	    qq/# switch\n/,
	    qq/node[shape="diamond", style=""];\n/;
	foreach my $i (grep {$_->{command} eq "switch"} values(%$nodelist)) {
		push @ret,
		    qq/$i->{ID}\[label="$i->{args}", group="$i->{level}"\];\n/;
	}
	push @ret, "\n";
	
	# do
	push @ret,
	    qq/# do\n/,
	    qq/node[shape="rect", style=""];\n/;
	foreach my $i (grep {$_->{command} eq "do"} values(%$nodelist)) {
		push @ret,
		    qq/$i->{ID}\[label="$i->{args}", group="$i->{level}"\];\n/;
	}
	push @ret, "\n";
	
	# call
	push @ret,
	    qq/# call\n/,
	    qq/node[shape="record", style=""];\n/;
	foreach my $i (grep {$_->{command} eq "call"} values(%$nodelist)) {
		push @ret,
		    qq/$i->{ID}\[label="\\ |$i->{args}|\\ ", group="$i->{level}"\];\n/;
	}
	push @ret, "\n";
	
	# start_end
	push @ret,
	    qq/# start_end\n/,
	    qq/node[shape="rect", style="rounded"];\n/;
	foreach my $i (grep {$_->{command} eq "start_end"} values(%$nodelist)) {
		push @ret,
		    qq/$i->{ID}\[label="$i->{args}", group="$i->{level}"\];\n/;
	}
	push @ret, "\n";
	
	# return
	push @ret,
	    qq/# goto(and return)\n/,
	    qq/node[shape="point", height=0, width=0];\n/;
	foreach my $i (grep {$_->{command} eq "goto"} values(%$nodelist)) {
		push @ret,
		    qq/$i->{ID}\[group="$i->{level}"\];\n/;
	}
	push @ret, "\n";
	
	# label
	push @ret,
	    qq/# label\n/,
	    qq/node[shape="point", height=0, width=0];\n/;
	foreach my $i (grep {$_->{command} eq "label"} values(%$nodelist)) {
		push @ret,
		    qq/$i->{ID}\[group="$i->{level}"\];\n/;
	}
	push @ret, "\n";
	
	# edge
	foreach my $i (values(%$nodelist)) {
		foreach my $ref (@{$i->{ref}}) {
			push @ret,
			    qq/$ref->[0] -> $i->{ID}/;
			
			push @ret,
			    qq/[label="$ref->[1]"]/
			    if $ref->[1];
			
			if ((@{$i->{ref}} == 1) && ($i->{command} eq "goto")) {
				push @ret,
				    qq/[arrowhead="none"]/;
			}
			if (($nodelist->{$ref->[0]}{command} ne "goto") && ($i->{command} eq "label")) {
				push @ret,
				    qq/[arrowhead="none"]/;
			}
			if (($nodelist->{$ref->[0]}{command} eq "goto") && (not $ref->[1])) {
				push @ret,
				    qq/[headport=e, constraint=false]/;
			}
			
			push @ret, ";\n";
		}
	}
	push @ret, "\n";
	
	# rank
	foreach my $i (grep {$_->{command} eq "switch"} values(%$nodelist)) {
		my @temp;
		push @ret,
		    qq/{rank=same;/;
		foreach my $j (values(%$nodelist)) {
			next if not @{$j->{ref}};
			if (grep {$_ eq $i->{ID}} map {$_->[0]} @{$j->{ref}}) {
				push @ret,
				    qq/$j->{ID};/;
				
				foreach my $k (values(%$nodelist)) {
					next if @{$k->{ref}} != 1;
					push @temp,
					    qq/$k->{ID}/
					    if grep {$_ eq $j->{ID}} map {$_->[0]} @{$k->{ref}};
				}
			}
		}
		push @ret,
		    qq/}\n/;
		push @ret,
		    qq/{rank=same;/,
		    join(";", @temp),
		    qq/}\n/;
	}
	push @ret, "\n";
	
	
	return \@ret;
} 


__DATA__
sub main2
	do init
label startSwitch
	switch OK?
	#return a
	case 1
		call error_func
		return a
	case 2
		break
	case 4
		do foo
		break
	case X
		goto startSwitch
	endswitch
endsub
