summaryrefslogtreecommitdiff
path: root/mcon/pl/tsort.pl
diff options
context:
space:
mode:
Diffstat (limited to 'mcon/pl/tsort.pl')
-rw-r--r--mcon/pl/tsort.pl166
1 files changed, 166 insertions, 0 deletions
diff --git a/mcon/pl/tsort.pl b/mcon/pl/tsort.pl
new file mode 100644
index 0000000..4d56fae
--- /dev/null
+++ b/mcon/pl/tsort.pl
@@ -0,0 +1,166 @@
+;# $Id$
+;#
+;# Copyright (c) 1991-1997, 2004-2006, Raphael Manfredi
+;#
+;# You may redistribute only under the terms of the Artistic Licence,
+;# as specified in the README file that comes with the distribution.
+;# You may reuse parts of this distribution only within the terms of
+;# that same Artistic Licence; a copy of which may be found at the root
+;# of the source tree for dist 4.0.
+;#
+;# $Log: tsort.pl,v $
+;# Revision 3.0 1993/08/18 12:10:28 ram
+;# Baseline for dist 3.0 netwide release.
+;#
+;#
+;# The topological sort is performed using the following algorithm:
+;#
+;# We have a list of successors for each item; makefile dependencies of
+;# the form 'a b: c d' means successors(a) = successors(b) = { c, d }.
+;# From that input, we derive a number of precursors for each item.
+;# In our simple example above, c and d both have two precursors and
+;# a and b have none. Items with no precursors are called outsiders
+;# and are left in a pool. The sort is then initiated and will continue
+;# until all the items have been sorted or a cycle is found...
+;#
+;# Outsiders are ready to be sorted; since the topological sort is a partial
+;# order, an external criterion is needed to choose one item among the ones
+;# in the pool. That item is assigned a number, and the number of precursors
+;# for the remaining items is updated (by following the successors of the
+;# sorted item and decrementing the value for each successor). Among those,
+;# if any item reaches a precursor count of zero, it becomes an outsider.
+;#
+;# The algorithm ends when the outsider pool is empty. If it becomes empty and
+;# some items remain unsorted, then there is one or more cycles among them.
+;# One way to outline that cycle first extract all those items whose precursor
+;# count is minimal then visit their dependency graph to find the cycle,
+;# extract only those items belonging to the cycle into the outsiders set and
+;# resume the main processing stream.
+;#
+#
+# Topological sort of Makefile dependencies with cycle enhancing.
+#
+
+package tsort;
+
+# Perform the topological sort of the items and outline cycles.
+sub main'tsort {
+ local(*Succ, *Prec) = @_; # Tables of succesors and predecessors
+ local(@Out); # The outsider set
+ local(@keys); # Current active precursors
+ local($item); # Item to sort
+
+ for (@keys = keys %Prec; @keys || @Out; @keys = keys %Prec) {
+ &resync; # Resynchronize outsiders
+ if (@Out == 0) { # Cycle detected
+ &extract_cycle(*Prec, *Succ);
+ next;
+ }
+ $item = shift(@Out); # Sort current item (don't care which one)
+ &sort($item); # Update internal structures
+ }
+}
+
+# Resynchronize the outsiders stack (those items that have no more precursors).
+# If the outsiders stack becomes empty, then there is a cycle.
+sub resync {
+ foreach $target (keys %Prec) {
+ if ($Prec{$target} == 0) {
+ delete $Prec{$target}; # We're done with this item
+ push(@Out, $target); # Ready to be sorted
+ }
+ }
+}
+
+# Sort item
+sub sort {
+ local($item) = @_;
+ print "(ok) $item\n" if $main'opt_d && !$Cycle;
+ print "(fx) $item\n" if $main'opt_d && $Cycle;
+ foreach $succ (split(' ', $Succ{$item})) {
+ # The test for definedness is necessary, since when a cycle is found,
+ # one item is forced out of %Prec. If we had the guarantee of no
+ # cycle, the the test would not be necessary and no decrementation
+ # could go past 0.
+ $Prec{$succ}-- if defined $Prec{$succ};
+ }
+}
+
+# Extract cycle... We look through the %Prec array and find all those items
+# with the same lowest value. Those are a cycle, so we dump them, and make
+# them new outsiders by resetting their count to 0.
+sub extract_cycle {
+ local(*Prec, *Succ) = @_;
+ local($item) = (&sort_by_value(*Prec))[0];
+ local($min) = $Prec{$item}; # Minimum value
+ local($key, $value);
+ local(%candidate); # Superset of the cycle we found
+ warn " Cycle found for:\n";
+ $Cycle++;
+ while (($key, $value) = each %Prec) {
+ $candidate{$key}++ if $value == $min;
+ }
+ local(%state); # State of visited nodes (1 = cycle, -1 = dead)
+ local($CYCLE) = 1; # Possible member of a cycle
+ local($DEAD) = -1; # Dead end, no cycling possible
+ foreach $key (keys %candidate) {
+ last if $CYCLE == &visit($key, $Succ{$key});
+ }
+ while (($key, $value) = each %candidate) {
+ next unless $state{$key} == $CYCLE;
+ $Prec{$key} = 0; # Members of cycle are new outsiders
+ warn "\t(#$Cycle) $key\n";
+ }
+ local(%involved); # Items involved in the cycle...
+ while (($key, $value) = each %state) {
+ $involved{$key}++ if $state{$key} == $CYCLE;
+ }
+ &outline_cycle(*Succ, *involved);
+}
+
+sub outline_cycle {
+ local(*Succ, *member) = @_;
+ local($key, $value);
+ local($depends);
+ local($unit);
+ warn " Cycle involves:\n";
+ while (($key, $value) = each %Succ) {
+ next unless $member{$key};
+ $depends = '';
+ foreach $item (split(' ', $value)) {
+ $depends .= "$item " if $member{$item};
+ }
+ $unit = $main'shmaster{"\$$key"};
+ $unit =~ s/\s+$//;
+ $unit = '?' if $unit eq '';
+ warn "\t($unit) $key: $depends\n";
+ }
+}
+
+# Visit a tree node, following all its successors, until we find a cycle.
+# Return $CYCLE if the exploration of the node leaded to a cycle, $DEAD
+# otherwise.
+sub visit {
+ local($node, $children) = @_; # A node and its children
+ # If we have already visited the node, return the status value attached
+ # to it.
+ return $state{$node} if $state{$node};
+ $state{$node} = $CYCLE; # Assume member of cycle
+ local($all_dead) = 1; # Set to 0 if at least one cycle found
+ foreach $child (split(' ', $children)) {
+ $all_dead = 0 if $CYCLE == &visit($child, $Succ{$child});
+ }
+ $state{$node} = $DEAD if $all_dead;
+ $state{$node};
+}
+
+# Sort associative array by value
+sub sort_by_value {
+ local(*x) = @_;
+ sub _by_value { $x{$a} <=> $x{$b}; }
+ sort _by_value keys %x;
+}
+
+package main;
+
+1;