use warnings; use feature 'state'; use strict; no strict 'refs'; use Data::Dumper; my $MAX_BINARY = 4; my @CANONICALS = qw(O S P N); my %foo = ( composer => 'S', subgraphs => [ { composer => 'O' }, { composer => 'P', subgraphs => [ { composer => 'O' }, { composer => 'O' }, ] }, { composer => 'N', subgraphs => [ { composer => 'O' }, { composer => 'O' }, { composer => 'P', subgraphs => [ { composer => 'O' }, { composer => 'O' }, { composer => 'O' }, { composer => 'O' }, ] }, { composer => 'O' } ] } ] ); # my $dot = to_dot(%foo); my $dot = to_dot(%{gen(2)}); $dot =~ s/^/ /gm; print "strict digraph {\nrankdir=BT;\n$dot\n}"; sub gen { my $n = shift; return { composer => 'O' } if $n == 0; my $rand = int(rand(4)); return { composer => 'O' } if $rand == 0; $n--; my $composer; my $num_subgraphs; if ($rand == 1) { $composer = 'S'; $num_subgraphs = int(rand(4))+1; } if ($rand == 2) { $composer = 'P'; $num_subgraphs = int(rand(4))+1; } if ($rand == 3) { $composer = 'N'; $num_subgraphs = 4; } my @subgraphs; for (my $i = 0; $i < $num_subgraphs; $i++) { push @subgraphs, gen($n); } return { composer => $composer, subgraphs => [ @subgraphs ] }; } sub to_dot { state $nodes = 0; my %graph = @_; return $nodes++ if ($graph{composer} eq 'O'); my $inner = &{$graph{composer}}(@{$graph{subgraphs}}); $inner =~ s/^/ /gm; return "subgraph { \n$inner\n}"; } sub S { my @rendered = map { to_dot(%$_) } @_; return join "\n->\n", @rendered; } sub P { my @rendered = map { to_dot(%$_) } @_; return join "\n;\n", @rendered; } sub N { my @rendered = map { to_dot(%$_) } @_; my $composed = ""; $composed .= "{ $rendered[0] $rendered[1] }\n->\n$rendered[2]"; $composed .= "\n;\n"; $composed .= "$rendered[1]\n->\n$rendered[3]"; return $composed; }