This function is very similar to Section 28.4, “
EqBind.lookup(): What is the referenced
taxon?”, except that it keeps track of which direct equivalent
codes have been seen so far, so as to avoid an infinite loop.
# - - - E q B i n d . _ _ c h a i n C l o s u r e def __chainClosure ( self, seenAbbrs, sym ): '''Find a taxon at sym or what it refers to [ (seenAbbrs is a set of bird codes) and (sym is an AbSym instance) -> if sym is a direct equivalent that is in seenAbbrs or leads indirectly to a code in seenAbbrs -> raise ValueError else if sym is a direct equivalent that is not in seenAbbrs and leads indirectly to a taxon -> return that taxon else -> raise ValueError ] NB: In the last case, the chain leads to a CollBind or unbound symbol. '''
First we need to eliminate the case where
does not even have a binding.
#-- 1 -- # [ if sym is unbound -> # raise ValueError # else -> # symBind = sym.binding symBind = sym.binding if symBind is None: raise ValueError ( "Code '%s' is referenced to code " "'%s', which is undefined." % (self.abbr, sym.abbr) )
sym is another
symAbbr to that instance's code.
symBind is not an
instance, we can call its
to see if the trail leads eventually to a taxon; if not,
that method returns
#-- 2 -- # [ if symBind is an EqBind -> # symAbbr := symBind.abbr # else if symBind defines a taxon -> # return that taxon as a Taxon instance # else -> raise ValueError ] if isinstance(symBind, EqBind): symAbbr = symBind.abbr else: taxon = symBind.lookup() if taxon is None: raise ValueError ( "Code '%s' is not associated " "with a taxon." % self.abbr ) else: return taxon
At this point we know that
sym is a direct
equivalent. Check for an infinite loop.
#-- 3 -- # [ if symAbbr is an element of seenAbbrs -> # raise ValueError # else -> # newSet := union ( seenAbbrs, set([symAbbr]) ) ] if symAbbr in seenAbbrs: raise ValueError ( "Code '%s' leads to a circuit of " "references." % self.abbr ) else: newSet = seenAbbrs.union ( set ( [symAbbr] ) )
Add the new bird code from
sym to the set of
codes we have seen, then call ourself recursively to chase the
reference chain the rest of the way.
#-- 4 -- # [ symBind is an EqBind -> # if symAbbr is directly equivalent to a code that is in # union(seenAbbrs, set([symAbbr]) -> # raise ValueError # else if sym is a direct equivalent that is not in # seenAbbrs and leads indirectly to a taxon -> # return that taxon # else -> raise ValueError ] return self.__chainClosure ( newSet, symBind.prefSym )