# Copyright (C) 1997-2022 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . package Automake::DisjConditions; use 5.006; use strict; use warnings FATAL => 'all'; use Carp; use Automake::Condition qw (TRUE FALSE); =head1 NAME Automake::DisjConditions - record a disjunction of Conditions =head1 SYNOPSIS use Automake::Condition; use Automake::DisjConditions; # Create a Condition to represent "COND1 and not COND2". my $cond = new Automake::Condition "COND1_TRUE", "COND2_FALSE"; # Create a Condition to represent "not COND3". my $other = new Automake::Condition "COND3_FALSE"; # Create a DisjConditions to represent # "(COND1 and not COND2) or (not COND3)" my $set = new Automake::DisjConditions $cond, $other; # Return the list of Conditions involved in $set. my @conds = $set->conds; # Return one of the Condition involved in $set. my $cond = $set->one_cond; # Return true iff $set is always true (i.e. its subconditions # cover all cases). if ($set->true) { ... } # Return false iff $set is always false (i.e. is empty, or contains # only false conditions). if ($set->false) { ... } # Return a string representing the DisjConditions. # "COND1_TRUE COND2_FALSE | COND3_FALSE" my $str = $set->string; # Return a human readable string representing the DisjConditions. # "(COND1 and !COND2) or (!COND3)" my $str = $set->human; # Merge (OR) several DisjConditions. my $all = $set->merge($set2, $set3, ...) # Invert a DisjConditions, i.e., create a new DisjConditions # that complements $set. my $inv = $set->invert; # Multiply two DisjConditions. my $prod = $set1->multiply ($set2); # Return the subconditions of a DisjConditions with respect to # a Condition. See the description for a real example. my $subconds = $set->sub_conditions ($cond); # Check whether a new definition in condition $cond would be # ambiguous w.r.t. existing definitions in $set. ($msg, $ambig_cond) = $set->ambiguous_p ($what, $cond); =head1 DESCRIPTION A C is a disjunction of Cs. In Automake they are used to represent the conditions into which Makefile variables and Makefile rules are defined. If the variable C is defined as if COND1 if COND2 VAR = value1 endif endif if !COND3 if COND4 VAR = value2 endif endif then it will be associated a C created with the following statement. new Automake::DisjConditions (new Automake::Condition ("COND1_TRUE", "COND2_TRUE"), new Automake::Condition ("COND3_FALSE", "COND4_TRUE")); As you can see, a C is made from a list of Cs. Since C is a disjunction, and C is a conjunction, the above can be read as follows. (COND1 and COND2) or ((not COND3) and COND4) That's indeed the condition in which C has a value. Like C objects, a C object is unique with respect to its conditions. Two C objects created for the same set of conditions will have the same address. This makes it easy to compare Cs: just compare the references. =head2 Methods =over 4 =item C<$set = new Automake::DisjConditions [@conds]> Create a C object from the list of C objects passed in arguments. If the C<@conds> list is empty, the C is assumed to be false. As explained previously, the reference (object) returned is unique with respect to C<@conds>. For this purpose, duplicate elements are ignored. =cut # Keys in this hash are DisjConditions strings. Values are the # associated object DisjConditions. This is used by 'new' to reuse # DisjConditions objects with identical conditions. our %_disjcondition_singletons; sub new ($;@) { my ($class, @conds) = @_; my @filtered_conds = (); for my $cond (@conds) { confess "'$cond' isn't a reference" unless ref $cond; confess "'$cond' isn't an Automake::Condition" unless $cond->isa ("Automake::Condition"); # This is a disjunction of conditions, so we drop # false conditions. We'll always treat an "empty" # DisjConditions as false for this reason. next if $cond->false; push @filtered_conds, $cond; } my $string; if (@filtered_conds) { @filtered_conds = sort { $a->string cmp $b->string } @filtered_conds; $string = join (' | ', map { $_->string } @filtered_conds); } else { $string = 'FALSE'; } # Return any existing identical DisjConditions. my $me = $_disjcondition_singletons{$string}; return $me if $me; # Else, create a new DisjConditions. # Store conditions as keys AND as values, because blessed # objects are converted to strings when used as keys (so # at least we still have the value when we need to call # a method). my %h = map {$_ => $_} @filtered_conds; my $self = { hash => \%h, string => $string, conds => \@filtered_conds, }; bless $self, $class; $_disjcondition_singletons{$string} = $self; return $self; } =item C Internal special subroutine to fix up the self hashes in C<%_disjcondition_singletons> upon thread creation. C is invoked automatically with ithreads from Perl 5.7.2 or later, so if you use this module with earlier versions of Perl, it is not thread-safe. =cut sub CLONE { foreach my $self (values %_disjcondition_singletons) { my %h = map { $_ => $_ } @{$self->{'conds'}}; $self->{'hash'} = \%h; } } =item C<@conds = $set-Econds> Return the list of C objects involved in C<$set>. =cut sub conds ($ ) { my ($self) = @_; return @{$self->{'conds'}}; } =item C<$cond = $set-Eone_cond> Return one C object involved in C<$set>. =cut sub one_cond ($) { my ($self) = @_; return (%{$self->{'hash'}},)[1]; } =item C<$et = $set-Efalse> Return 1 iff the C object is always false (i.e., if it is empty, or if it contains only false Cs). Return 0 otherwise. =cut sub false ($ ) { my ($self) = @_; return 0 == keys %{$self->{'hash'}}; } =item C<$et = $set-Etrue> Return 1 iff the C object is always true (i.e. covers all conditions). Return 0 otherwise. =cut sub true ($ ) { my ($self) = @_; return $self->invert->false; } =item C<$str = $set-Estring> Build a string which denotes the C. =cut sub string ($ ) { my ($self) = @_; return $self->{'string'}; } =item C<$cond-Ehuman> Build a human readable string which denotes the C. =cut sub human ($ ) { my ($self) = @_; return $self->{'human'} if defined $self->{'human'}; my $res = ''; if ($self->false) { $res = 'FALSE'; } else { my @c = $self->conds; if (1 == @c) { $res = $c[0]->human; } else { $res = '(' . join (') or (', map { $_->human } $self->conds) . ')'; } } $self->{'human'} = $res; return $res; } =item C<$newcond = $cond-Emerge (@otherconds)> Return a new C which is the disjunction of C<$cond> and C<@otherconds>. Items in C<@otherconds> can be @Cs or C. =cut sub merge ($@) { my ($self, @otherconds) = @_; new Automake::DisjConditions ( map { $_->isa ("Automake::DisjConditions") ? $_->conds : $_ } ($self, @otherconds)); } =item C<$prod = $set1-Emultiply ($set2)> Multiply two conditional sets. my $set1 = new Automake::DisjConditions (new Automake::Condition ("A_TRUE"), new Automake::Condition ("B_TRUE")); my $set2 = new Automake::DisjConditions (new Automake::Condition ("C_FALSE"), new Automake::Condition ("D_FALSE")); C<$set1-Emultiply ($set2)> will return new Automake::DisjConditions (new Automake::Condition ("A_TRUE", "C_FALSE"), new Automake::Condition ("B_TRUE", "C_FALSE"),; new Automake::Condition ("A_TRUE", "D_FALSE"), new Automake::Condition ("B_TRUE", "D_FALSE")); The argument can also be a C. =cut # Same as multiply() but take a list of Conditionals as second argument. # We use this in invert(). sub _multiply ($@) { my ($self, @set) = @_; my @res = map { $_->multiply (@set) } $self->conds; return new Automake::DisjConditions (Automake::Condition::reduce_or @res); } sub multiply ($$) { my ($self, $set) = @_; return $self->_multiply ($set) if $set->isa('Automake::Condition'); return $self->_multiply ($set->conds); } =item C<$inv = $set-Einvert> Invert a C. Return a C which is true when C<$set> is false, and vice-versa. my $set = new Automake::DisjConditions (new Automake::Condition ("A_TRUE", "B_TRUE"), new Automake::Condition ("A_FALSE", "B_FALSE")); Calling C<$set-Einvert> will return the following C. new Automake::DisjConditions (new Automake::Condition ("A_TRUE", "B_FALSE"), new Automake::Condition ("A_FALSE", "B_TRUE")); We implement the inversion by a product-of-sums to sum-of-products conversion using repeated multiplications. Because of the way we implement multiplication, the result of inversion is in canonical prime implicant form. =cut sub invert($ ) { my ($self) = @_; return $self->{'invert'} if defined $self->{'invert'}; # The invert of an empty DisjConditions is TRUE. my $res = new Automake::DisjConditions TRUE; # !((a.b)+(c.d)+(e.f)) # = (!a+!b).(!c+!d).(!e+!f) # We develop this into a sum of product iteratively, starting from TRUE: # 1) TRUE # 2) TRUE.!a + TRUE.!b # 3) TRUE.!a.!c + TRUE.!b.!c + TRUE.!a.!d + TRUE.!b.!d # 4) TRUE.!a.!c.!e + TRUE.!b.!c.!e + TRUE.!a.!d.!e + TRUE.!b.!d.!e # + TRUE.!a.!c.!f + TRUE.!b.!c.!f + TRUE.!a.!d.!f + TRUE.!b.!d.!f foreach my $cond ($self->conds) { $res = $res->_multiply ($cond->not); } # Cache result. $self->{'invert'} = $res; # It's tempting to also set $res->{'invert'} to $self, but that # is a bad idea as $self hasn't been normalized in any way. # (Different inputs can produce the same inverted set.) return $res; } =item C<$self-Esimplify> Return a C which is a simplified canonical form of C<$self>. This canonical form contains only prime implicants, but it can contain non-essential prime implicants. =cut sub simplify ($) { my ($self) = @_; return $self->invert->invert; } =item C<$self-Esub_conditions ($cond)> Return the subconditions of C<$self> that contains C<$cond>, with C<$cond> stripped. More formally, return C<$res> such that C<$res-Emultiply ($cond) == $self-Emultiply ($cond)> and C<$res> does not mention any of the variables in C<$cond>. For instance, consider: my $a = new Automake::DisjConditions (new Automake::Condition ("A_TRUE", "B_TRUE"), new Automake::Condition ("A_TRUE", "C_FALSE"), new Automake::Condition ("A_TRUE", "B_FALSE", "C_TRUE"), new Automake::Condition ("A_FALSE")); my $b = new Automake::DisjConditions (new Automake::Condition ("A_TRUE", "B_FALSE")); Calling C<$a-Esub_conditions ($b)> will return the following C. new Automake::DisjConditions (new Automake::Condition ("C_FALSE"), # From A_TRUE C_FALSE new Automake::Condition ("C_TRUE")); # From A_TRUE B_FALSE C_TRUE" =cut sub sub_conditions ($$) { my ($self, $subcond) = @_; # Make $subcond blindingly apparent in the DisjConditions. # For instance '$b->multiply($a->conds)' (from the POD example) is: # (new Automake::Condition ("FALSE"), # new Automake::Condition ("A_TRUE", "B_FALSE", "C_FALSE"), # new Automake::Condition ("A_TRUE", "B_FALSE", "C_TRUE"), # new Automake::Condition ("FALSE")) my @prodconds = $subcond->multiply ($self->conds); # Now, strip $subcond from the remaining (i.e., non-false) Conditions. my @res = map { $_->false ? () : $_->strip ($subcond) } @prodconds; return new Automake::DisjConditions @res; } =item C<($string, $ambig_cond) = $condset-Eambiguous_p ($what, $cond)> Check for an ambiguous condition. Return an error message and the other condition involved if we have an ambiguity. Return an empty string and FALSE otherwise. C<$what> is the name of the thing being defined, to use in the error message. C<$cond> is the C under which it is being defined. C<$condset> is the C under which it had already been defined. =cut sub ambiguous_p ($$$) { my ($self, $var, $cond) = @_; # Note that these rules don't consider the following # example as ambiguous. # # if COND1 # FOO = foo # endif # if COND2 # FOO = bar # endif # # It's up to the user to not define COND1 and COND2 # simultaneously. return ("$var multiply defined in condition " . $cond->human, $cond) if exists $self->{'hash'}{$cond}; foreach my $vcond ($self->conds) { return ("$var was already defined in condition " . $vcond->human . ", which includes condition ". $cond->human, $vcond) if $vcond->true_when ($cond); return ("$var was already defined in condition " . $vcond->human . ", which is included in condition " . $cond->human, $vcond) if $cond->true_when ($vcond); } return ('', FALSE); } =head1 SEE ALSO L. =head1 HISTORY Cs and supporting code were added to Automake 1.1o by Ian Lance Taylor in 1997. Since then it has been improved by Tom Tromey , Richard Boulton , Raja R Harinath , Akim Demaille , Pavel Roskin , and Alexandre Duret-Lutz . =cut 1;