def exhaust(brule):
    """ Apply a branching rule repeatedly until it has no effect """
    def exhaust_brl(expr):
        seen = {expr}
        for nexpr in brule(expr):
            if nexpr not in seen:
                seen.add(nexpr)
                for nnexpr in exhaust_brl(nexpr):
                    yield nnexpr
        if seen == {expr}:
            yield expr
    return exhaust_brl