Convert SRE regex parse tree to strategy that generates strings matching that
    regex represented by that parse tree.

    `codes` is either a list of SRE regex elements representations or a particular
    element representation. Each element is a tuple of element code (as string) and
    parameters. E.g. regex 'ab[0-9]+' compiles to following elements:

        [
            ('literal', 97),
            ('literal', 98),
            ('max_repeat', (1, 4294967295, [
                ('in', [
                    ('range', (48, 57))
                ])
            ]))
        ]

    The function recursively traverses regex element tree and converts each element
    to strategy that generates strings that match that element.

    Context stores
    1. List of groups (for backreferences)
    2. Active regex flags (e.g. IGNORECASE, DOTALL, UNICODE, they affect behavior
       of various inner strategies)

def _strategy(codes, context):
    """
    Convert SRE regex parse tree to strategy that generates strings matching that
    regex represented by that parse tree.

    `codes` is either a list of SRE regex elements representations or a particular
    element representation. Each element is a tuple of element code (as string) and
    parameters. E.g. regex 'ab[0-9]+' compiles to following elements:

        [
            ('literal', 97),
            ('literal', 98),
            ('max_repeat', (1, 4294967295, [
                ('in', [
                    ('range', (48, 57))
                ])
            ]))
        ]

    The function recursively traverses regex element tree and converts each element
    to strategy that generates strings that match that element.

    Context stores
    1. List of groups (for backreferences)
    2. Active regex flags (e.g. IGNORECASE, DOTALL, UNICODE, they affect behavior
       of various inner strategies)
    """
    if not isinstance(codes, tuple):
        # List of codes
        strategies = []

        i = 0
        while i < len(codes):
            if codes[i][0] == sre.LITERAL and not (context.flags & re.IGNORECASE):
                # Merge subsequent "literals" into one `just()` strategy
                # that generates corresponding text if no IGNORECASE
                j = i + 1
                while j < len(codes) and codes[j][0] == sre.LITERAL:
                    j += 1

                strategies.append(hs.just(
                    u''.join([six.unichr(charcode) for (_, charcode) in codes[i:j]])
                ))

                i = j
            else:
                strategies.append(_strategy(codes[i], context))
                i += 1

        return hs.tuples(*strategies).map(u''.join)
    else:
        # Single code
        code, value = codes
        if code == sre.LITERAL:
            # Regex 'a' (single char)
            c = six.unichr(value)
            if context.flags & re.IGNORECASE:
                return hs.sampled_from([c.lower(), c.upper()])
            else:
                return hs.just(c)

        elif code == sre.NOT_LITERAL:
            # Regex '[^a]' (negation of a single char)
            c = six.unichr(value)
            blacklist = set([c.lower(), c.upper()]) \
                if context.flags & re.IGNORECASE else [c]
            return hs.characters(blacklist_characters=blacklist)

        elif code == sre.IN:
            # Regex '[abc0-9]' (set of characters)
            charsets = value

            builder = CharactersBuilder(negate=charsets[0][0] == sre.NEGATE,
                                        flags=context.flags)

            for charset_code, charset_value in charsets:
                if charset_code == sre.NEGATE:
                    # Regex '[^...]' (negation)
                    pass
                elif charset_code == sre.LITERAL:
                    # Regex '[a]' (single char)
                    builder.add_chars(six.unichr(charset_value))
                elif charset_code == sre.RANGE:
                    # Regex '[a-z]' (char range)
                    low, high = charset_value
                    for x in six.moves.range(low, high+1):
                        builder.add_chars(six.unichr(x))
                elif charset_code == sre.CATEGORY:
                    # Regex '[\w]' (char category)
                    builder.add_category(charset_value)
                else:
                    raise he.InvalidArgument(
                        'Unknown charset code: %s' % charset_code
                    )

            return builder.strategy

        elif code == sre.ANY:
            # Regex '.' (any char)
            if context.flags & re.DOTALL:
                return hs.characters()
            else:
                return hs.characters(blacklist_characters="\n")

        elif code == sre.AT:
            # Regexes like '^...', '...$', '\bfoo', '\Bfoo'
            if value == sre.AT_END:
                return hs.one_of(hs.just(u''), hs.just(u'\n'))
            return hs.just('')

        elif code == sre.SUBPATTERN:
            # Various groups: '(...)', '(:...)' or '(?P<name>...)'
            old_flags = context.flags
            if HAS_SUBPATTERN_FLAGS:
                context.flags = (context.flags | value[1]) & ~value[2]

            strat = _strategy(value[-1], context)

            context.flags = old_flags

            if value[0]:
                context.groups[value[0]] = strat
                strat = hs.shared(strat, key=value[0])

            return strat

        elif code == sre.GROUPREF:
            # Regex '\\1' or '(?P=name)' (group reference)
            return hs.shared(context.groups[value], key=value)

        elif code == sre.ASSERT:
            # Regex '(?=...)' or '(?<=...)' (positive lookahead/lookbehind)
            return _strategy(value[1], context)

        elif code == sre.ASSERT_NOT:
            # Regex '(?!...)' or '(?<!...)' (negative lookahead/lookbehind)
            return hs.just('')

        elif code == sre.BRANCH:
            # Regex 'a|b|c' (branch)
            return hs.one_of([_strategy(branch, context) for branch in value[1]])

        elif code in [sre.MIN_REPEAT, sre.MAX_REPEAT]:
            # Regexes 'a?', 'a*', 'a+' and their non-greedy variants (repeaters)
            at_least, at_most, regex = value
            if at_most == 4294967295:
                at_most = None
            return hs.lists(_strategy(regex, context),
                            min_size=at_least,
                            max_size=at_most).map(''.join)

        elif code == sre.GROUPREF_EXISTS:
            # Regex '(?(id/name)yes-pattern|no-pattern)' (if group exists selection)
            return hs.one_of(
                _strategy(value[1], context),
                _strategy(value[2], context) if value[2] else hs.just(u''),
            )

        else:
            raise he.InvalidArgument('Unknown code point: %s' % repr(code))