def traverse_lexemes(expr, fn):
    """
    Apply a function to every expression in a lexeme tree, bottom-up.
    Eg. given [['a', 'and', 'b'], 'or', ['c', 'and', 'd']]
    It applies the function like: fn([fn(['a', 'and', 'b']), 'or', fn(['c', 'and', 'd'])])

    This is recursive. We're not worried about stack overflow here.
    """
    if isinstance(expr, str):
        return expr  # base case
    return fn([traverse_lexemes(term, fn) for term in expr])