Back to home page

Project CMSSW displayed by LXR

 
 

    


Warning, /Utilities/ReleaseScripts/ignominy/xmerge is written in an unsupported language. File is not indexed.

0001 #!/usr/bin/env perl
0002 use warnings;
0003 use strict;
0004 
0005 my $me = $0;
0006 $me =~ s|.*/||;
0007 
0008 ######################################################################
0009 my $SUM_INC_OUT_EDGES   = 0;
0010 my $SUM_INC_OUT_COUNT   = 1;
0011 my $SUM_INC_IN_EDGES    = 2;
0012 my $SUM_INC_IN_COUNT    = 3;
0013 my $SUM_LIB_OUT_EDGES   = 4;
0014 my $SUM_LIB_OUT_COUNT   = 5;
0015 my $SUM_LIB_IN_EDGES    = 6;
0016 my $SUM_LIB_IN_COUNT    = 7;
0017 my $SUM_LOG_OUT_EDGES   = 8;
0018 my $SUM_LOG_IN_EDGES    = 9;
0019 my $SUM_ALL_IN_EDGES    = 10;
0020 my $SUM_ALL_OUT_EDGES   = 11;
0021 my $SUM_N               = 12;
0022 
0023 my $CELL_INC_OUT_COUNT  = 0;
0024 my $CELL_INC_IN_COUNT   = 1;
0025 my $CELL_LIB_OUT_COUNT  = 2;
0026 my $CELL_LIB_IN_COUNT   = 3;
0027 my $CELL_LOG_OUT_EDGES  = 4;
0028 my $CELL_LOG_IN_EDGES   = 5;
0029 my $CELL_N              = 6;
0030 
0031 my @SUM_LABELS = ("Number of outgoing edges from includes",
0032                   "Total count of outgoing includes",
0033                   "Number of incoming edges from includes",
0034                   "Total count of incoming includes",
0035                   "Number of outgoing edges from symbols",
0036                   "Total count of outgoing symbols",
0037                   "Number of incoming edges from symbols",
0038                   "Total count of incoming symbols",
0039                   "Number of (possibly overlapping) outgoing logical edges",
0040                   "Number of (possibly overlapping) incoming logical edges",
0041                   "Total count of incoming edges",
0042                   "Total count of outgoing edges");
0043 
0044 sub min {
0045   my $min = shift;
0046   foreach my $val (@_) {
0047     $min = $min > $val ? $val : $min;
0048   }
0049   return $min;
0050 }
0051 
0052 sub max {
0053   my $max = shift;
0054   foreach my $val (@_) {
0055     $max = $max > $val ? $max : $val;
0056   }
0057   return $max;
0058 }
0059 
0060 sub rgb2hsv {
0061   my ($r, $g, $b) = @_;
0062   my ($h, $s, $v);
0063   my $max = &max($r, $g, $b);
0064   my $min = &min($r, $g, $b);
0065 
0066   $v = $max;
0067   $s = ($max ? ($max - $min)/$max : 0.0);
0068   if (! $s) {
0069     $h = 0;
0070   } else {
0071     my $d = $max - $min;
0072     if ($r == $max) {
0073       $h = 0. + ($g - $b)/$d;
0074     } elsif ($g == $max) {
0075       $h = 2. + ($b - $r)/$d;
0076     } elsif ($b == $max) {
0077       $h = 4. + ($r - $g)/$d;
0078     }
0079     $h /= 6.;
0080     $h += 1. if ($h < 0.);
0081   }
0082 
0083   $h = int($h * 1000 + .5)/1000;
0084   $s = int($s * 1000 + .5)/1000;
0085   $v = int($v * 1000 + .5)/1000;
0086 
0087   return ($h, $s, $v);
0088 }
0089 
0090 sub rgb2yiq {
0091   my ($r, $g, $b) = @_;
0092   return (.299 * $r + .587 * $g + .115 * $b,
0093           .596 * $r - .275 * $g - .321 * $b,
0094           .212 * $r - .523 * $g + .311 * $b);
0095 }
0096 
0097 sub hsv2rgb {
0098   my ($h, $s, $v) = @_;
0099   if ($s eq 0.) {
0100     die "bad HSV H value: $h -- should be 0\n" if $h;
0101     return ($v, $v, $v);
0102   } else {
0103     $h = 0. if $h eq 1.;
0104     $h *= 6.;
0105     my $i = int($h);
0106     my $f = $h - $i;
0107     my $p = $v * (1. - $s);
0108     my $q = $v * (1. - $s * $f);
0109     my $t = $v * (1. - $s * (1. - $f));
0110 
0111     return ($v, $t, $p) if $i == 0;
0112     return ($q, $v, $p) if $i == 1;
0113     return ($p, $v, $t) if $i == 2;
0114     return ($p, $q, $v) if $i == 3;
0115     return ($t, $p, $v) if $i == 4;
0116     return ($v, $p, $q) if $i == 5;
0117     die;
0118   }
0119 }
0120 
0121 sub validate_flag {
0122   my ($value, $opt) = @_;
0123 
0124   return 1 if (grep(lc($value) eq $_, qw(1 y yes)));
0125   return 0 if (grep(lc($value) eq $_, qw(0 n no)));
0126   die "$opt must be (1, y, yes) or (0, n, no)\n";
0127 }
0128 
0129 sub file_part {
0130   my $name = shift;
0131   $name =~ s|.*/||;
0132   return $name;
0133 }
0134 
0135 sub dir_part {
0136   my $name = shift;
0137   return '' if $name !~ m|/|;
0138   $name =~ s|/[^/]+$||;
0139   return $name;
0140 }
0141 
0142 sub check_xedge {
0143   my ($edges, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target) = @_;
0144   return 0 if (grep($target =~ /$_/, @$rrx) && 1) ne $rinclusive;
0145 
0146   my $matches = 0;
0147   foreach my $xrx (@$xrx) {
0148     $matches += scalar grep ($_ ne ':hard'
0149                              && ($xinclusive ? $_ =~ /$xrx/ : $_ !~ /$xrx/),
0150                              @{$edges->{$source}->{$target}});
0151   }
0152   return $matches;
0153 }
0154 
0155 sub include_edge {
0156   my ($edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target) = @_;
0157   return 0 if !exists $nodes->{$target};
0158   return 0 if (grep($target =~ /$_/, @$rrx) && 1) ne $rinclusive;
0159   return 1 if grep($_ eq ':hard', @{$edges->{$source}->{$target}});
0160   return 2 if &check_xedge($edges,$rinclusive,$rrx,$xinclusive,$xrx,$source,$target) ne 0;
0161   return 0;
0162 }
0163 
0164 sub do_node {
0165   my ($gedges,$gnodes, $edges,$nodes, $rinclusive,$rrx, $xinclusive,$xrx, $from,$to,$include)=@_;
0166   return if ! $include;
0167 
0168   if (defined $from) {
0169     $gedges->{$from} ||= {};
0170     $gedges->{$from}->{$to} = $include
0171       if (! exists $gedges->{$from}->{$to}
0172           || $gedges->{$from}->{$to} > $include);
0173   }
0174 
0175   return if (exists $gnodes->{$to});
0176 
0177   $gnodes->{$to} = 1;
0178   foreach (keys %{$edges->{$to}}) {
0179     &do_node($gedges, $gnodes, $edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $to, $_,
0180              &include_edge($edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $to, $_));
0181   }
0182 }
0183 
0184 sub group_node {
0185   my ($grouping, $groups, $node, $default) = @_;
0186   foreach my $group (@$groups) {
0187     my $i = 0;
0188     foreach (@{$group->[2]}) {
0189       if ($node =~ /$_/) {
0190         $grouping->{$node} = [ $group, $i ];
0191         return;
0192       }
0193       $i++;
0194     }
0195   }
0196   $grouping->{$node} = [ $default, 0 ];
0197 }
0198 
0199 my %renames_results = ();
0200 sub rename_node {
0201   my ($renames, $node) = @_;
0202   if (exists $renames_results{$node}){return $renames_results{$node};}
0203   for (@$renames){
0204     if ($node =~ /$_->[0]/){
0205       my $tmp=eval $_->[1];
0206       $renames_results{$node}=$tmp;
0207       $renames_results{$tmp}=$tmp;
0208       return $tmp;
0209     }
0210   }
0211   $renames_results{$node}=$node;
0212   return $node;
0213 }
0214 
0215 sub dep_count {
0216   my ($table, $i, $j, $row) = @_;
0217   my $x = $table->[$i]->[$j];
0218   my $y = $table->[$j]->[$i];
0219 
0220   my ($libs, $incs);
0221   if ($row->[3] eq 'libraries') {
0222     $libs = $row->[2];
0223     $incs = $row->[5];
0224   } else {
0225     $libs = $row->[5];
0226     $incs = $row->[2];
0227   }
0228 
0229   $x->[$CELL_LIB_OUT_COUNT] += $libs;
0230   $y->[$CELL_LIB_IN_COUNT] += $libs;
0231 
0232   $x->[$CELL_INC_OUT_COUNT] += $incs;
0233   $y->[$CELL_INC_IN_COUNT] += $incs;
0234 }
0235 
0236 sub dep_xcount {
0237   my ($edges, $rinclusive, $rrx, $xinclusive, $xrx, $labels, $table, $i, $j) = @_;
0238   my $source = $labels->[$i];
0239   my $target = $labels->[$j];
0240   my $matches = &check_xedge($edges, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target);
0241   if ($matches ne 0) {
0242     $table->[$i]->[$j]->[$CELL_LOG_OUT_EDGES] += $matches;
0243     $table->[$j]->[$i]->[$CELL_LOG_IN_EDGES]  += $matches;
0244   }
0245 }
0246 
0247 sub dep_summarise {
0248   my ($summary, $labels, $table, $i) = @_;
0249 
0250   foreach my $cell (@{$table->[$i]}) {
0251     $summary->[$i]->[$SUM_INC_OUT_EDGES]++ if ($cell->[$CELL_INC_OUT_COUNT]);
0252     $summary->[$i]->[$SUM_INC_OUT_COUNT]   += $cell->[$CELL_INC_OUT_COUNT];
0253     $summary->[$i]->[$SUM_INC_IN_EDGES]++  if ($cell->[$CELL_INC_IN_COUNT]);
0254     $summary->[$i]->[$SUM_INC_IN_COUNT]    += $cell->[$CELL_INC_IN_COUNT];
0255 
0256     $summary->[$i]->[$SUM_LIB_OUT_EDGES]++ if ($cell->[$CELL_LIB_OUT_COUNT]);
0257     $summary->[$i]->[$SUM_LIB_OUT_COUNT]   += $cell->[$CELL_LIB_OUT_COUNT];
0258     $summary->[$i]->[$SUM_LIB_IN_EDGES]++  if ($cell->[$CELL_LIB_IN_COUNT]);
0259     $summary->[$i]->[$SUM_LIB_IN_COUNT]    += $cell->[$CELL_LIB_IN_COUNT];
0260 
0261     $summary->[$i]->[$SUM_LOG_OUT_EDGES]   += $cell->[$CELL_LOG_OUT_EDGES];
0262     $summary->[$i]->[$SUM_LOG_IN_EDGES]    += $cell->[$CELL_LOG_IN_EDGES];
0263 
0264     $summary->[$i]->[$SUM_ALL_IN_EDGES]++
0265       if ($cell->[$CELL_INC_IN_COUNT]
0266           || $cell->[$CELL_LIB_IN_COUNT]
0267           || $cell->[$CELL_LOG_IN_EDGES]);
0268 
0269     $summary->[$i]->[$SUM_ALL_OUT_EDGES]++
0270       if ($cell->[$CELL_INC_OUT_COUNT]
0271           || $cell->[$CELL_LIB_OUT_COUNT]
0272           || $cell->[$CELL_LOG_OUT_EDGES]);
0273   }
0274 }
0275 
0276 sub dep_metrics_mark {
0277   my ($edges, $tedges, $n,$m) = @_;
0278   return if defined $m && exists $tedges->{$n}->{$m};
0279   if (defined $m) {
0280     $tedges->{$n}->{$m} = 1;
0281     map { &dep_metrics_mark($edges, $tedges, $n, $_) } keys %{$edges->{$m}};
0282   }
0283   map { &dep_metrics_mark($edges, $tedges, $n, $_) } keys %{$edges->{$n}};
0284 }
0285 
0286 sub dep_metrics {
0287   # see ftp://ftp.aw.com/cp/lakos/idep_ldep.c
0288   my ($edges, $nodes, $metrics) = @_;
0289 
0290   my @nodes     = keys %$nodes;
0291   my $n         = scalar @nodes;
0292   my @levels    = (0) x $n;
0293   my $nlevels   = 1;
0294   my @levelnums = (0) x $n;
0295   my @mapping   = (0) x $n;
0296   my $nmapping  = 0;
0297 
0298   my @lowerthan = map { [ (0) x $n ] } @nodes, 'foo', 'bar';
0299    
0300   # calculate transitive closure
0301   my %tedges = map { $_ => {} } keys %$edges;
0302   map { &dep_metrics_mark($edges, \%tedges, $_, undef) } keys %$edges;
0303 
0304   # determine and label all members of all cycles
0305   my @cycles    = (-1) x $n;
0306   my @weights   = (0) x $n;
0307   my @cindices  = (-1) x $n;
0308   my $ncycles   = 0;
0309   my $nmembers  = 0;
0310   for (my $i = 0; $i < $n; ++$i) {
0311     next if $cycles[$i] >= 0;
0312     my $found = 0;
0313     $cycles[$i] = $i;
0314     for (my $j = $i + 1; $j < $n; ++$j) {
0315       next if $cycles[$j] >= 0;
0316       if ($tedges{$nodes[$i]}->{$nodes[$j]} && $tedges{$nodes[$j]}->{$nodes[$i]}) {
0317         $found = 1;
0318         $cycles[$j] = $i;
0319       }
0320     }
0321     if ($found) {
0322       my $weight = 0;
0323       for (my $j = $i; $j < $n; ++$j) {
0324         ++$weight if $cycles[$j] == $i;
0325       }
0326       for (my $j = $i; $j < $n; ++$j) {
0327         $weights[$j] = $weight if $cycles[$j] == $i;
0328       }
0329       $nmembers += $weight;
0330       $ncycles++;
0331     } else {
0332       $cycles[$i] = -1;
0333     }
0334   }
0335 
0336   # sort packages into levelized order; strip principal cycle
0337   # members from their dependencies on other cycle members
0338   for (my $i = 0; $i < $n; ++$i) {
0339     next if $cycles[$i] != $i;
0340     for (my $j = $i + 1; $j < $n; ++$j) {
0341       next if $cycles[$j] != $i;
0342       delete $tedges{$nodes[$i]}->{$nodes[$j]};
0343       $lowerthan[1]->[$j] = 1;
0344     }
0345   }
0346 
0347   # construct levelized array of package indices
0348   while ($nmapping < $n) {
0349     my $count = 0;
0350     my @current = (0) x $n;
0351     for (my $i = 0; $i < $n; ++$i) {
0352       next if $cycles[$i] >= 0 && $cycles[$i] != $i;
0353       next if $lowerthan[$nlevels]->[$i];
0354 
0355       my $weight = 1;
0356       if ($cycles[$i] == $i) {
0357         next if $weights[$i] > $nlevels + 1;
0358         $weight = $weights[$i];
0359       }
0360 
0361       my $level = $nlevels + 1 - $weight;
0362       my $j;
0363       for ($j = 0; $j < $n; ++$j) {
0364         next if $i == $j;
0365         last if $tedges{$nodes[$i]}->{$nodes[$j]} && !$lowerthan[$level]->[$j];
0366       }
0367       next if $j < $n;
0368 
0369       $current[$i] = 1;
0370       $mapping[$nmapping++] = $i;
0371       $count++;
0372       if ($cycles[$i] == $i) {
0373         for ($j = $i + 1; $j < $n; ++$j) {
0374           next if $cycles[$j] != $i;
0375           $mapping[$nmapping++] = $j;
0376           $count++;
0377           $tedges{$nodes[$i]}->{$nodes[$j]} = 1;
0378         }
0379       }
0380     }
0381     for (my $i = 0; $i < $n; ++$i) {
0382       $current[$i] ||= $lowerthan[$nlevels][$i];
0383     }
0384     $levels[$nlevels++] = $count;
0385     @{$lowerthan[$nlevels]} = @current;
0386     @current = (0) x $n;
0387   }
0388   die "internal error" if $nmapping != $n;
0389 
0390   # start loading level number array
0391   my $start = 0;
0392   for (my $i = 1; $i < $nlevels; ++$i) {
0393     my $top = $start + $levels[$i];
0394     for (my $j = $start; $j < $top; ++$j) {
0395       $levelnums[$mapping[$j]] = $i;
0396     }
0397     $start = $top;
0398   }
0399 
0400   # sort packages on each level lexicographically
0401   $start = 0;
0402   for (my $k = 1; $k < $nlevels; ++$k) {
0403     my $top = $start + $levels[$k];
0404     for (my $i = $start + 1; $i < $top; ++$i) {
0405       for (my $j = $start; $j < $i; ++$j) {
0406         if (($nodes[$mapping[$i]] cmp $nodes[$mapping[$j]]) < 0) {
0407           my $tmp = $mapping[$i];
0408           $mapping[$i] = $mapping[$j];
0409           $mapping[$j] = $tmp;
0410         }
0411       }
0412     }
0413     $start = $top;
0414   }
0415 
0416   # create @cindices from cycles array and the level map
0417   my $ncycle = 0;
0418   for (my $i = 0; $i < $n; ++$i) {
0419     my $label = $cycles[$mapping[$i]];
0420     next if $label < 0;
0421     my $index = $cindices[$mapping[$i]];
0422     next if $index >= 0 && $index < $ncycle;
0423     for (my $j = $i; $j < $n; ++$j) {
0424       $cindices[$mapping[$j]] = $ncycle if $label == $cycles[$mapping[$j]];
0425     }
0426     $ncycle++;
0427   }
0428   die "internal error" if $ncycle != $ncycles;
0429 
0430   # sort packages on each level again but now grouping
0431   # cyclically dependent packages together
0432   $start = 0;
0433   for (my $k = 1; $k < $nlevels; ++$k) {
0434     my $top = $start + $levels[$k];
0435     for (my $i = $start + 1; $i < $top; ++$i) {
0436       for (my $j = $start; $j < $i; ++$j) {
0437         my $ci = $cindices[$mapping[$i]];
0438         my $cj = $cindices[$mapping[$j]];
0439         if ($ci < $cj || ($ci == $cj && ($nodes[$mapping[$i]] cmp $nodes[$mapping[$j]]) < 0)) {
0440           my $tmp = $mapping[$i];
0441           $mapping[$i] = $mapping[$j];
0442           $mapping[$j] = $tmp;
0443         }
0444       }
0445     }
0446     $start = $top;
0447   }
0448 
0449   # calculate ccd
0450   my %tmp = map { $_ => {} } keys %tedges;
0451   foreach my $n (keys %tedges) {
0452     map { $tmp{$n}->{$_} = 1 } keys %{$tedges{$n}};
0453   }
0454 
0455   for (my $i = 0; $i < $n; ++$i) {
0456     if ($levelnums[$i] == 0) {
0457       for (my $j = 0; $j < $n; ++$j) {
0458         delete $tmp{$nodes[$j]}->{$nodes[$i]};
0459       }
0460     } else {
0461       $tmp{$nodes[$i]}->{$nodes[$i]} = 1;
0462     }
0463   }
0464 
0465   my $ccd = 0;
0466   map { $ccd += scalar keys %{$tmp{$_}} } keys %tmp;
0467 
0468   if (0) {
0469     # make canonical representation: remove all redundant (transitive0edges
0470     my @tmp = map { [ (0) x $n ] } @nodes;
0471     for (my $i = 0; $i < $n; ++$i) {
0472       my $u = $mapping[$i];
0473       for (my $j = 0; $j < $n; ++$j) {
0474         my $v = $mapping[$j];
0475         my $bit = $tedges{$nodes[$u]}->{$nodes[$v]};
0476         $tmp[$i]->[$j] = $bit;
0477       }
0478     }
0479     # FIXME;
0480   }
0481 
0482   $metrics->{NODES}     = \@nodes;
0483   $metrics->{N}         = $n;
0484   $metrics->{LEVELS}    = \@levels;
0485   $metrics->{NLEVELS}   = $nlevels;
0486   $metrics->{LEVELNUMS} = \@levelnums;
0487   $metrics->{MAPPING}   = \@mapping;
0488   $metrics->{NMAPPINGS} = $nmapping;
0489    
0490   $metrics->{TEDGES}    = \%tedges;
0491 
0492   $metrics->{CYCLES}    = \@cycles;
0493   $metrics->{WEIGHTS}   = \@weights;
0494   $metrics->{CINDICES}  = \@cindices;
0495   $metrics->{NCYCLES}   = $ncycles;
0496   $metrics->{NMEMBERS}  = $nmembers;
0497 
0498   $metrics->{CCD}       = $ccd;
0499 
0500   return $nmembers;
0501 }
0502 
0503 my %url_results = ();
0504 sub node_url {
0505   my ($urls, $name) = @_;
0506   if (exists $url_results{$name}){return $url_results{$name};}
0507   foreach my $mapping (@$urls) {
0508     if ($name =~ /$mapping->[0]/) {
0509       my $N = $name;
0510       my $tmp = eval $mapping->[1];
0511       $url_results{$name} = $tmp;
0512       return $tmp;
0513     }
0514   }
0515   $url_results{$name} = undef;
0516   return undef;
0517 }
0518 
0519 sub read_report {
0520   my ($edges, $nodes, $file, $part) = @_;
0521   my $node = undef;
0522 
0523   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0524   while (<FILE>) {
0525     last if /^\# $part/;
0526   }
0527 
0528   while (<FILE>) {
0529     chomp;
0530     last if (/^\#\#\#\#\#/);
0531     next if (/^\s*$/);
0532 
0533     if (/^\S/) {
0534       chop;
0535       s% ([^/]+)$%%;
0536       $node = $_;
0537       $nodes->{$_} = 1;
0538       $edges->{$_} ||= {};
0539     } elsif (/^\s+\S/) {
0540       print STDERR "$file: warning: no current node for a dependency\n"
0541         if ! $node;
0542 
0543       s/\s//g;
0544       $nodes->{$_} = 1;
0545       $edges->{$_} ||= {}; 
0546       $edges->{$node}->{$_} ||= [];
0547       push(@{$edges->{$node}->{$_}}, ':hard');
0548     } else {
0549       print STDERR "$file: warning: unrecognized line: `$_'\n";
0550     }
0551   }
0552   close(FILE);
0553 }
0554 
0555 sub keep_nodes {
0556   my ($kept_nodes, $edges, $node) = @_;
0557   $kept_nodes->{$node} = 1;
0558   foreach my $edge (keys %{$edges->{$node}}) {
0559     if (!exists $kept_nodes->{$edge}){
0560       &keep_nodes ($kept_nodes, $edges, $edge);
0561     }
0562   }
0563 }
0564 
0565 sub remove_nodes {
0566   my ($edges, $nodes, $spart) = @_;
0567   my %kept_nodes = ();
0568   my $node;
0569   foreach $node (keys %$nodes) {
0570     if (($node=~/$spart/) && (!exists $kept_nodes{$node})){
0571       &keep_nodes (\%kept_nodes, $edges, $node);
0572     }
0573   }
0574   foreach $node (keys %$nodes) {
0575     if (!exists $kept_nodes{$node}){
0576       delete $nodes->{$node};
0577       delete $edges->{$node};
0578     }
0579   }
0580 }
0581 
0582 sub recalculate {
0583   my ($renames, $edges, $nodes, $vector) = @_;
0584   my %indexs = ();
0585   my @newvec = ();
0586   foreach my $item (@$vector){
0587     my $node = &rename_node($renames, $item->[0]);
0588     my $edge = &rename_node($renames, $item->[1]);
0589     my $count = $item->[2];
0590     next if (!exists $edges->{$node}->{$edge});
0591     if (!exists $indexs{"$node:$edge"}){
0592       $indexs{"$node:$edge"} = @newvec;
0593       $item->[0]=$node;
0594       $item->[1]=$edge;
0595       push(@newvec, $item);      
0596     }
0597     else{
0598       my $index = $indexs{"$node:$edge"};
0599       my $pcount = $newvec[$index]->[2];
0600       $newvec[$index]->[2] = $pcount + $count;
0601     }
0602   }
0603   while (@$vector > 0){pop @$vector;}
0604   foreach my $item (@newvec){push (@$vector, $item);}
0605 }
0606 
0607 sub apply_renames {
0608   my ($renames, $edges, $nodes) = @_;
0609   my %nnodes = ();
0610   foreach my $node (keys %$nodes){
0611     my $nnode = &rename_node($renames, $node);
0612     $nnodes{$nnode} ||= {};
0613     foreach my $edge (keys %{$edges->{$node}}){
0614       my $nedge = &rename_node($renames, $edge);
0615       $nnodes{$nnode}->{$nedge} = 1;
0616     }
0617     delete $nodes->{$node};
0618     delete $edges->{$node};
0619   }
0620   foreach my $node (keys %nnodes){
0621     foreach my $edge (keys %{$nnodes{$node}}){
0622       $nodes->{$node} = 1; $edges->{$node} ||= {};
0623       $nodes->{$edge} = 1; $edges->{$edge} ||= {};
0624       $edges->{$node}->{$edge} ||= [];
0625       if(!exists $edges->{$node}->{$edge}->[0]){
0626         push(@{$edges->{$node}->{$edge}}, ':hard');
0627       }
0628     }
0629   }
0630 }
0631 
0632 sub read_counts {
0633   my ($renames, $edges, $nodes, $vector, $note, $file) = @_;
0634   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0635   while (<FILE>) {
0636     chomp;
0637     if (! /^\s*(\d+)\s+(\S+)\s+(\S+(,\s+\S+)?)(\s+via\s+(\S+))?$/) {
0638       print STDERR "$file: error: bad line `$_'\n";
0639       next;
0640     }
0641     my $node=$2;
0642     my $edge=$3;
0643     next if &rename_node($renames, $node) eq &rename_node($renames, $edge);
0644     $nodes->{$node} = 1; $edges->{$node} ||= {};
0645     $nodes->{$edge} = 1; $edges->{$edge} ||= {};
0646     $edges->{$node}->{$edge} ||= [];
0647     push(@{$edges->{$node}->{$edge}}, ':hard');
0648     push(@$vector, [ $node, $edge, $1, $note, $5 || '', -1]);
0649   }
0650   close(FILE);
0651 }
0652 
0653 sub read_loglinks {
0654   my ($edges, $nodes, $file) = @_;
0655   my @xtargets = ();
0656   my @xreasons = ();
0657 
0658   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0659   while (<FILE>) {
0660     chomp; s/\#.*//; s/\s*$//;
0661     next if (/^\s*$/);
0662 
0663     if (/^(\S+(\s*,\s*\S+)*)(\s*:\s*->\s*(\S+(\s*,\s*\S+)*))?$/) {
0664       @xreasons = split(/\s*,\s*/, $1);
0665       @xtargets = split(/\s*,\s*/, $4 || '');
0666     } elsif (/^\s+(\S+(\s*,\s*\S+)*)(\s*->\s*(\S+(\s*,\s*\S+)*))?$/) {
0667       my @sources = split(/\s*,\s*/, $1);
0668       my @targets = ($4 ? split(/\s*,\s*/, $4) : @xtargets);
0669       foreach my $source (grep(exists $nodes->{$_}, @sources)) {
0670         $edges->{$source} ||= {};
0671         map { $edges->{$source}->{$_} ||= [];
0672               push(@{$edges->{$source}->{$_}}, @xreasons); } @targets;
0673       }
0674     } else {
0675       print STDERR "$file: warning: unrecognized line: `$_'\n";
0676     }
0677   }
0678   close(FILE);
0679 }
0680 
0681 sub read_groups {
0682   my ($groups, $file) = @_;
0683   my $i = 0;
0684 
0685   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0686   while (<FILE>) {
0687     chomp;
0688     while (/\\$/ && !eof(FILE)) {
0689       chop; $_ .= <FILE>; chomp;
0690     }
0691     if (/\\$/) {
0692       print STDERR "$file: warning: incomplete last line\n";
0693       chop;
0694     }
0695     s/\#.*//; s/^\s*//; s/\s*$//;
0696     next if (/^$/);
0697 
0698     if (/^group\s+(\S+)\s+(.*)\s+--\s+(.*)$/) {
0699       push(@$groups, [ $i++, $1, [ split(/\s+/, $2) ], $3 ]);
0700     } else {
0701       print STDERR "$file: warning: unrecognized line: `$_'\n";
0702     }
0703   }
0704   close(FILE);
0705 }
0706 
0707 sub read_renames {
0708   my ($renames, $file) = @_;
0709   my $i = 0;
0710 
0711   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0712   while (<FILE>) {
0713     chomp;
0714     while (/\\$/ && !eof(FILE)) {
0715       chop; $_ .= <FILE>; chomp;
0716     }
0717     if (/\\$/) {
0718       print STDERR "$file: warning: incomplete last line\n";
0719       chop;
0720     }
0721     s/\#.*//; s/^\s*//; s/\s*$//;
0722     next if (/^$/);
0723 
0724     if (/^rename\s+([^:]+):(.*)$/) {
0725       push(@$renames, [ $1, $2 ]);
0726     } else {
0727       print STDERR "$file: warning: unrecognized line: `$_'\n";
0728     }
0729   }
0730   close(FILE);
0731 }
0732 
0733 sub read_urls {
0734   my ($urls, $file) = @_;
0735   my $i = 0;
0736 
0737   open(FILE, $file) || die "$file: error: cannot open: $!\n";
0738   while (<FILE>) {
0739     chomp;
0740     while (/\\$/ && !eof(FILE)) {
0741       chop; $_ .= <FILE>; chomp;
0742     }
0743     if (/\\$/) {
0744       print STDERR "$file: warning: incomplete last line\n";
0745       chop;
0746     }
0747     s/^\s*//; s/\s*$//;
0748     next if (/^$/);
0749 
0750     if (/^([^:]+):(.*)$/) {
0751       push(@$urls, [ $1, $2 ]);
0752     } else {
0753       print STDERR "$file: warning: unrecognized line: `$_'\n";
0754     }
0755   }
0756   close(FILE);
0757 }
0758 
0759 ######################################################################
0760 my $usage =
0761     "usage: $me [--text|--html|--side-by-side] [--group=FILE] [--rename=FILE]\n"
0762   . "                INCS LIBS FILTER INCLUSIVE? [RE...]\n"
0763   . "                [! RESTRICT-INCLUSIVE? RESTRICT-RE...]\n"
0764   . "                [-- EXTRA EXTRA-INCLUSIVE? EXTRA-RE...]\n";
0765 
0766 my $mode = 'text';
0767 my $groupfile = '/dev/null';
0768 my $renamefile = '/dev/null';
0769 my $default_group = [ -1, 'other', [ '.*' ], '1 1 1' ];
0770 
0771 while (scalar @ARGV && $ARGV[0] =~ /^--(.*)$/) {
0772   my $arg = $1;
0773   if ($1 eq 'text' || $1 eq 'html' || $1 eq 'side-by-side') {
0774     $mode = $1;
0775   } elsif ($1 =~ /^group=(.*)/) {
0776     $groupfile = $1;
0777   } elsif ($1 =~ /^rename=(.*)/) {
0778     $renamefile = $1;
0779   } else {
0780     print STDERR "unrecognised option `$arg'\n";
0781     die $usage;
0782   }
0783   shift(@ARGV);
0784 }
0785 
0786 die $usage if (scalar @ARGV < 3);
0787 my ($incfile, $libfile, $filter, $inclusive, @restargs) = @ARGV;
0788 my ($rinclusive, $xfile, $xinclusive, @rx, @rrx, @xrx) = (1, "/dev/null", 1);
0789 $inclusive = &validate_flag($inclusive, "INCLUSIVE?");
0790 
0791 while (scalar @restargs) {
0792   my $arg = shift(@restargs);
0793   if ($arg eq '!') {
0794     die $usage if (scalar @restargs < 2);
0795     $rinclusive = &validate_flag(shift(@restargs), "RESTRICT-INCLUSIVE?");
0796     while (scalar @restargs && $restargs[0] ne '--') {
0797       push(@rrx, shift(@restargs));
0798     }
0799   } elsif ($arg eq '--') {
0800     die $usage if (scalar @restargs < 2);
0801     $xfile = shift(@restargs);
0802     $xinclusive = &validate_flag(shift(@restargs), "EXTRA-INCLUSIVE?");
0803     @xrx = @restargs;
0804     $xrx[0] ||= '.*';
0805     last;
0806   } else {
0807     push(@rx, $arg);
0808   }
0809 }
0810 $rx[0] ||= '.*';
0811 $rrx[0] ||= '.*';
0812 
0813 ######################################################################
0814 my @incdeps = ();
0815 my @libdeps = ();
0816 my %nodes = ();
0817 my %gnodes = ();
0818 my %edges = ();
0819 my %gedges = ();
0820 my @roots = ();
0821 my @all = ();
0822 my @groups = ();
0823 my %grouping = ();
0824 my @renames = ();
0825 
0826 # Read in renames.
0827 &read_renames(\@renames, $renamefile);
0828 
0829 # Read in the files.  The count files are output from "sort | uniq -c".
0830 &read_counts(\@renames, \%edges, \%nodes, \@incdeps, 'includes', $incfile);
0831 &read_counts(\@renames, \%edges, \%nodes, \@libdeps, 'libraries', $libfile);
0832 
0833 # Read in the extra-deps file.
0834 &read_loglinks(\%edges, \%nodes, $xfile);
0835 
0836 # Remove the unwanted nodes
0837 &remove_nodes(\%edges, \%nodes, $filter) if ($filter ne "");
0838 &apply_renames(\@renames, \%edges, \%nodes);
0839 
0840 # recalculate the incdeps, libdeps
0841 &recalculate (\@renames, \%edges, \%nodes, \@incdeps);
0842 &recalculate (\@renames, \%edges, \%nodes, \@libdeps);
0843 
0844 # Read in grouping.
0845 &read_groups(\@groups, $groupfile);
0846 
0847 # Calculate which nodes can be reached.
0848 foreach my $node (keys %nodes) {
0849   push(@roots, $node) if (grep($node =~ /$_/, @rx) && 1) eq $inclusive;
0850 }
0851 
0852 foreach (@roots) {
0853   &do_node(\%gedges,\%gnodes, \%edges,\%nodes, $rinclusive,\@rrx, $xinclusive,\@xrx, undef,$_,1);
0854 }
0855 @all = grep(exists $gnodes{$_->[0]}, @incdeps, @libdeps);
0856 
0857 # Group nodes
0858 map { &group_node(\%grouping, \@groups, $_, $default_group) } keys %nodes;
0859 
0860 ######################################################################
0861 # Interleave and merge dependency counts.
0862 my @mixed = sort { $a->[0] cmp $b->[0]
0863                    || $a->[1] cmp $b->[1]
0864                    || $a->[3] cmp $b->[3]
0865                    || $a->[4] cmp $b->[4] } @all;
0866 
0867 for (my ($i, $skip) = (0, 0); $i < scalar @mixed; ++$i) {
0868   if ($skip) {
0869     $skip = 0;
0870     next;
0871   }
0872 
0873   if ($mixed[$i]->[3] eq 'includes'
0874       && $mixed[$i]->[4] eq ''
0875       && $i < scalar @mixed - 1
0876       && $mixed[$i+1]->[0] eq $mixed[$i]->[0]
0877       && $mixed[$i+1]->[1] eq $mixed[$i]->[1]) {
0878     $mixed[$i]->[5] = $mixed[$i+1]->[2];
0879     $skip = 1;
0880   } else {
0881     $mixed[$i]->[5] = 0;
0882   }
0883 }
0884 
0885 my @merged = grep { $_->[5] >= 0 } @mixed;
0886 
0887 ######################################################################
0888 # Tabulate dependency counts.
0889 my %packages = ();
0890 map { $packages{$_->[0]} = 1; $packages{$_->[1]} = 1; } @merged;
0891 
0892 my $i = 0;
0893 my @labels = ();
0894 foreach (sort { $a->[1] cmp $b->[1] }
0895          map { [ $_, &rename_node(\@renames, $_) ] }
0896          keys %packages) {
0897   push(@labels, $_->[0]);
0898   $packages{$_->[0]} = $i++;
0899 }
0900 
0901 my $nlabels = $i;
0902 my $longest = &max(map { length($_) } @labels) || 0;
0903 my @table = map { [ map { [ (0) x $CELL_N ] } @labels ] } @labels;
0904 my @summary = map { [ (0) x $SUM_N ] } @labels;
0905 
0906 foreach (@merged) {
0907   &dep_count(\@table, $packages{$_->[0]}, $packages{$_->[1]}, $_);
0908 }
0909 
0910 for (my $i = 0; $i < $nlabels; ++$i) {
0911   for (my $j = 0; $j < $nlabels; ++$j) {
0912     &dep_xcount(\%edges, $rinclusive, \@rrx, $xinclusive, \@xrx, \@labels, \@table, $i, $j);
0913   }
0914 }
0915 
0916 for (my $i = 0; $i < $nlabels; ++$i) {
0917   &dep_summarise(\@summary, \@labels, \@table, $i);
0918 }
0919 
0920 ######################################################################
0921 # Print out dependency counts.
0922 my $clear = '';
0923 my $shadow = ' bgcolor="#eeeeee"';
0924 
0925 sub cell_label {
0926   my ($cell, $part) = @_;
0927   die "internal error: no such part $part"
0928     if ! grep($part eq $_, qw(includes libraries logical all));
0929 
0930   if ($part eq 'all') {
0931     my $val = "";
0932     if ($cell->[$CELL_LIB_OUT_COUNT] || $cell->[$CELL_INC_OUT_COUNT]) {
0933       $val .= "$cell->[$CELL_LIB_OUT_COUNT]/$cell->[$CELL_INC_OUT_COUNT]";
0934     }
0935     if ($cell->[$CELL_LOG_OUT_EDGES]) {
0936       $val .= "/" if $val;
0937       $val .= "x$cell->[$CELL_LOG_OUT_EDGES]";
0938     }
0939     return $val;
0940   } elsif ($part eq 'libraries') {
0941     return $cell->[$CELL_LIB_OUT_COUNT];
0942   } elsif ($part eq 'includes') {
0943     return $cell->[$CELL_INC_OUT_COUNT];
0944   } elsif ($part eq 'logical') {
0945     return $cell->[$CELL_LOG_OUT_EDGES];
0946   }
0947 }
0948 
0949 sub print_html_table {
0950   my ($part, $caption, $dobreak) = @_;
0951 
0952   my $break = $dobreak ? " class='page-break'" : "";
0953   print "<?page-break?>\n" if $dobreak;
0954   print "<H3$break>$caption</H3>\n" if $caption;
0955   for (my $N = 0; $N*40 < $nlabels; ++$N) {
0956     print "<TABLE BORDER='0'>\n";
0957     print "  <TR><TH$shadow>&nbsp;</TH><TH$shadow>&nbsp;</TH>";
0958     for (my $j = $N*40; $j < $nlabels && $j < ($N+1)*40; ++$j) {
0959       print "<TH$shadow><A onMouseOver=\"window.status=\'".&rename_node(\@renames, $labels[$j])."\'; return true\">" . ($j+1) . "</A></TH>";
0960     }
0961     print "</TR>\n";
0962 
0963     for (my $i = 0; $i < $nlabels; ++$i) {
0964       print "  <TR><TH$shadow>" . ($i+1) . "</TH><TH align='left'$shadow>"
0965         . &rename_node(\@renames, $labels[$i]) . "</TH>";
0966       for (my $j = $N*40; $j < $nlabels && $j < ($N+1)*40; ++$j) {
0967         print "<TD" . (($i + $j) % 2 ? $shadow : $clear) . "><A onMouseOver=\"window.status=\'".&rename_node(\@renames, $labels[$j])."\'; return true\">";
0968         print (&cell_label($table[$i]->[$j], $part) || '&nbsp;');
0969         print "</A></TD>";
0970       }
0971       print "</TR>\n";
0972     }
0973     print "</TABLE>\n";
0974   }
0975 }
0976 
0977 sub print_html_summary {
0978   my $caption = shift;
0979 
0980   print "<?page-break?>\n";
0981   print "<H3 class='page-break'>$caption</H3>\n" if $caption;
0982   print "<OL>\n";
0983   print map { "  <LI> $_\n" } @SUM_LABELS;
0984   print "</OL>\n";
0985   print "<TABLE BORDER='0'>\n";
0986   print "  <TR><TH$shadow>&nbsp;</TH>";
0987   for (my $i = 0; $i < $SUM_N; ++$i) {
0988     print "<TH$shadow><A onMouseOver=\"window.status=\'".$SUM_LABELS[$i]."\'; return true\">". ($i+1) . "</A></TH>";
0989   }
0990   print "</TR>\n";
0991 
0992   my $in  = 0;
0993   my $out = 0;
0994   for (my $i = 0; $i < $nlabels; ++$i) {
0995     $in  += $summary[$i]->[$SUM_ALL_IN_EDGES];
0996     $out += $summary[$i]->[$SUM_ALL_OUT_EDGES];
0997 
0998     print "  <TR><TH align='left'$shadow>" . &rename_node(\@renames, $labels[$i]) . "</TH>";
0999     for(my $x=0;$x<scalar(@{$summary[$i]});$x++)
1000     {
1001       my $v=$summary[$i][$x];
1002       print "<TD" . ($i % 8 >= 4 ? $shadow : "") . "><A onMouseOver=\"window.status=\'".$SUM_LABELS[$x]."\'; return true\">$v</TD>";
1003       #print map { "<TD" . ($i % 8 >= 4 ? $shadow : "") . ">$_</TD>" }
1004       #  @{$summary[$i]};
1005     }
1006     print "</TR>\n";
1007   }
1008   # NB: $in == $out by now
1009   print "  <TR>\n";
1010   print "    <TD>&nbsp;</TD>\n";
1011   print "    <TD COLSPAN='@{[$SUM_N-2]}'>Average # of outgoing edges</TD>\n";
1012   if ($nlabels){
1013     print "    <TD COLSPAN='2'>" . sprintf("%.2f", $out / $nlabels) . "</TD>\n";
1014   }
1015   print "  </TR>\n";
1016   print "</TABLE>\n";
1017 }
1018 
1019 if ($mode eq 'text') {
1020   print '#' x 70, "\n# Mixed dependencies\n\n";
1021   foreach (@mixed) {
1022     print sprintf "%-12s", "$_->[2]";
1023     print "$_->[0] $_->[1] $_->[3] $_->[4]\n";
1024   }
1025 
1026   print "\n", '#' x 70, "\n# Merged dependencies\n\n";
1027   foreach (@merged) {
1028     print sprintf "%-12s",
1029       &cell_label($table[$packages{$_->[0]}] ->[$packages{$_->[1]}], 'all');
1030     print "$_->[0] $_->[1] $_->[4]\n";
1031   }
1032 
1033   print "\n", '#' x 70, "\n# Edge summary\n";
1034   for (my $i = 0; $i < $SUM_N; ++$i) {
1035     print "# (@{[$i+1]}) $SUM_LABELS[$i]\n";
1036   }
1037   print "\n";
1038   my $width  = (int(($longest+2)/8)+1)*8;
1039   my $format = "%-" . $width . "s";
1040   print sprintf $format, "# PACKAGE";
1041   print join("\t", map { "($_)" } (1 .. $SUM_N)), "\n";
1042   print "# ", '-' x ($width + $SUM_N * 8 - 2), "\n";
1043 
1044   my $out = 0;
1045   for (my $i = 0; $i < $nlabels; ++$i) {
1046     $out += $summary[$i]->[$SUM_ALL_OUT_EDGES];
1047     print sprintf $format, $labels[$i];
1048     print join("\t", @{$summary[$i]}), "\n";
1049   }
1050   print "# ", '-' x ($width + $SUM_N * 8 - 2), "\n";
1051   print sprintf $format, "# PACKAGE";
1052   print join("\t", map { "($_)" } (1 .. $SUM_N)), "\n";
1053   if ($nlabels){
1054     print "\nAverage # of outgoing edges: " . sprintf("%.2f", $out / $nlabels) . "\n";
1055   }
1056 } elsif ($mode eq 'html') {
1057   &print_html_table('libraries', "Symbol dependencies", 0);
1058   &print_html_table('includes', "Header file dependencies", 1);
1059   &print_html_table('logical', "Logical dependencies", 1) if $xfile ne '/dev/null';
1060   &print_html_table('all', "All dependencies", 1);
1061   &print_html_summary("Summary");
1062 } elsif ($mode eq 'side-by-side') {
1063   # reorder the nodes according to grouping; use group color
1064   my @order = sort { $grouping{$a}->[0]->[0] cmp $grouping{$b}->[0]->[0]
1065                      || $grouping{$a}->[1] cmp $grouping{$b}->[1]
1066                      || &file_part($a) cmp &file_part($b)
1067                      || $a cmp $b } @labels;
1068   my %ordering = ();
1069   for (my $i = 0; $i < $nlabels; ++$i) {
1070     $ordering{$order[$i]} = $i;
1071   }
1072 
1073   my $maxheight = 800;
1074   my $maxwidth  = 550;
1075   my $fontsize  = 8;
1076   my $boxheight = $fontsize + 4;
1077   my $freeheight= max(0, $maxheight - ($nlabels+1) * $boxheight);
1078   my $boxsep    = int(100 * $freeheight / ($nlabels+1))/100;
1079   my $boxwidth  = $fontsize * 20;
1080   my $boxoff    = 2;
1081   my $sidesplit = $maxwidth - 2*$boxwidth;
1082   my $height    = ($nlabels + 1) * ($boxheight + $boxsep);
1083   my $llx       = 20;
1084   my $lly       = 20;
1085   my @bbox      = ($llx, $lly,
1086                    int($llx + $sidesplit + $boxwidth*2 + .5),
1087                    int($lly + $height + .5));
1088   my %boxes     = ();
1089 
1090   for (my $i = 0; $i < $nlabels; ++$i) {
1091     $boxes{$order[$i]} =
1092       [ $llx, $lly + $height - ($i+1)*($boxheight+$boxsep) - $boxheight ];
1093   }
1094 
1095   my $ps_preamble =
1096     ("/dobox {\n" .
1097      "  /tb exch def /ts exch def /th exch def\n" .
1098      "  /bb exch def /bs exch def /bh exch def\n" .
1099      "  currentpoint /y exch def /x exch def\n" .
1100      "  newpath x y moveto boxwidth 0 rlineto 0 boxheight rlineto\n" .
1101      "  boxwidth neg 0 rlineto closepath\n" .
1102      "  gsave bh bs bb sethsbcolor fill grestore\n".
1103      "  0 0 0 sethsbcolor stroke\n" .
1104      "  x y moveto boxoff boxoff rmoveto\n" .
1105      "  th ts tb sethsbcolor show } bind def\n" .
1106      "/dashed { [9 9] 0 setdash } bind def\n" .
1107      "/dotted { [1 6] 0 setdash } bind def\n" .
1108      "/solid  { [] 0 setdash } bind def\n" .
1109      "/doline { moveto lineto cvx sethsbcolor stroke pop } bind def");
1110 
1111   print "%!PS-Adobe-3.0\n";
1112   print "%%BoundingBox: ", join(" ", @bbox), "\n";
1113   print "%%DocumentNeededFonts: font Helvetica Helvetica-Bold\n";
1114   print "%%BeginProlog\n$ps_preamble\n%%EndProlog\n";
1115   print "%%Pages: 1\n";
1116   print "%%Page: 1 1\n";
1117   print "/boxwidth $boxwidth def\n";
1118   print "/boxheight $boxheight def\n";
1119   print "/boxoff $boxoff def\n";
1120   print "save $llx $lly moveto\n";
1121   print "(Helvetica-Bold) findfont 8 scalefont setfont\n";
1122         
1123   foreach (@order) {
1124     my $name = &rename_node(\@renames, $_);
1125     my @hsvcolor = split(/\s+/, $grouping{$_}->[0]->[3]);
1126     my $y = (&rgb2yiq(&hsv2rgb(@hsvcolor)))[0];
1127     my $textcolor = ($y < .5 ? "0 0 1" : "0 0 0");
1128     print "\n($name) $boxes{$_}->[0] $boxes{$_}->[1] moveto "
1129       . join(" ", @hsvcolor) . " $textcolor dobox";
1130     print "\n($name) " . ($boxwidth+$sidesplit+$boxes{$_}->[0])
1131       . " $boxes{$_}->[1] moveto " . join(" ", @hsvcolor) . " $textcolor dobox";
1132   }
1133   print "\n\n(Helvetica-Bold) findfont 10 scalefont setfont 0 0 0 sethsbcolor\n";
1134   print sprintf "%d %d moveto (%s) show\n",
1135     $llx, $lly + $height - $boxheight, "Out";
1136   print sprintf "%d %d moveto (%s) show\n",
1137     $llx + $sidesplit + $boxwidth, $lly + $height - $boxheight, "In";
1138   print "(Helvetica) findfont 8 scalefont setfont\n";
1139 
1140   for (my $i = 0; $i < $nlabels; ++$i) {
1141     for (my $j = 0; $j < $nlabels; ++$j) {
1142       my $val = &cell_label($table[$i]->[$j], 'all');
1143       next if (! $val);
1144 
1145       print sprintf "(%s) %s %d %d %d %d doline\n",
1146         $val, ($val =~ m|^x| ? '0 0 .5' : '0 0 0'),
1147         $boxes{$order[$ordering{$labels[$i]}]}->[0] + $boxwidth,
1148         $boxes{$order[$ordering{$labels[$i]}]}->[1] + $boxheight / 2,
1149         $boxes{$order[$ordering{$labels[$j]}]}->[0] + $sidesplit + $boxwidth,
1150         $boxes{$order[$ordering{$labels[$j]}]}->[1] + $boxheight / 2;
1151     }
1152   }
1153   print "restore showpage\n";
1154   print "%%EOF\n";
1155 } else {
1156   die "internal error: unknown mode $mode";
1157 }