<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def filter(filter, data, sensitive=True, regexp=False):
 """ Return true if provided filter matches given dictionary of values Filter supports disjunctive normal form with '|' used for OR, '&' for AND and '-' for negation. Individual values are prefixed with 'value:', leading/trailing white-space is stripped. For example:: tag: Tier1 | tag: Tier2 | tag: Tier3 category: Sanity, Security & tag: -destructive Note that multiple comma-separated values can be used as a syntactic sugar to shorten the filter notation:: tag: A, B, C ---> tag: A | tag: B | tag: C Values should be provided as a dictionary of lists each describing the values against which the filter is to be matched. For example:: data = {tag: ["Tier1", "TIPpass"], category: ["Sanity"]} Other types of dictionary values are converted into a string. A FilterError exception is raised when a dimension parsed from the filter is not found in the data dictionary. Set option 'sensitive' to False to enable case-insensitive matching. If 'regexp' option is True, regular expressions can be used in the filter values as well. """


    def match_value(pattern, text):
        """ Match value against data (simple or regexp) """
        if regexp:
            return re.match("^{0}$".format(pattern), text)
        else:
            return pattern == text

    def check_value(dimension, value):
        """ Check whether the value matches data """
        # E.g. value = 'A, B' or value = "C" or value = "-D"
        # If there are multiple values, at least one must match
        for atom in re.split("\s*,\s*", value):
            # Handle negative values (check the whole data for non-presence)
            if atom.startswith("-"):
                atom = atom[1:]
                # Check each value for given dimension
                for dato in data[dimension]:
                    if match_value(atom, dato):
                        break
                # Pattern not found ---> good
                else:
                    return True
            # Handle positive values (return True upon first successful match)
            else:
                # Check each value for given dimension
                for dato in data[dimension]:
                    if match_value(atom, dato):
                        # Pattern found ---> good
                        return True
        # No value matched the data
        return False

    def check_dimension(dimension, values):
        """ Check whether all values for given dimension match data """
        # E.g. dimension = 'tag', values = ['A, B', 'C', '-D']
        # Raise exception upon unknown dimension
        if dimension not in data:
            raise FilterError("Invalid filter '{0}'".format(dimension))
        # Every value must match at least one value for data
        return all([check_value(dimension, value) for value in values])

    def check_clause(clause):
        """ Split into literals and check whether all match """
        # E.g. clause = 'tag: A, B & tag: C & tag: -D'
        # Split into individual literals by dimension
        literals = dict()
        for literal in re.split("\s*&\s*", clause):
            # E.g. literal = 'tag: A, B'
            # Make sure the literal matches dimension:value format
            matched = re.match("^(.*)\s*:\s*(.*)$", literal)
            if not matched:
                raise FilterError("Invalid filter '{0}'".format(literal))
            dimension, value = matched.groups()
            values = [value]
            # Append the literal value(s) to corresponding dimension list
            literals.setdefault(dimension, []).extend(values)
        # For each dimension all literals must match given data
        return all([check_dimension(dimension, values)
            for dimension, values in literals.items()])

    # Default to True if no filter given, bail out if weird data given
    if filter is None or filter == "": return True
    if not isinstance(data, dict):
        raise FilterError("Invalid data type '{0}'".format(type(data)))

    # Make sure that data dictionary contains lists of strings
    data = copy.deepcopy(data)
    try: # pragma: no cover
        for key in data:
            if isinstance(data[key], list):
                data[key] = [unicode(item) for item in data[key]]
            else:
                data[key] = [unicode(data[key])]
    except NameError: # pragma: no cover
        for key in data:
            if isinstance(data[key], list):
                data[key] = [str(item) for item in data[key]]
            else:
                data[key] = [str(data[key])]
    # Turn all data into lowercase if sensitivity is off
    if not sensitive:
        filter = filter.lower()
        lowered = dict()
        for key, values in data.items():
            lowered[key.lower()] = [value.lower() for value in values]
        data = lowered

    # At least one clause must be true
    return any([check_clause(clause)
            for clause in re.split("\s*\|\s*", filter)])