def generate_strategies(
        table: generate_property_table.Table) -> Dict[str, SymbolicStrategy]:
    # variable identifier ->  strategy how to generate the identifier
    strategies: Dict[str, SymbolicStrategy] = dict()

    for row in table.get_rows():
        if row.kind == generate_property_table.Kind.BASE:
            strategies[row.var_id] = _infer_strategy(row, table)
    return strategies
def _infer_int_strategy(
        row: generate_property_table.Row,
        table: generate_property_table.Table) -> SymbolicIntegerStrategy:
    max_value: List[Property] = []
    min_value: List[Property] = []
    filters: List[Lambda] = []

    min_value_contains_free_variables = False
    max_value_contains_free_variables = False

    for property_identifier, row_property in row.properties.items():
        if property_identifier == '<':
            row_property_decremented = _decrement_property(row_property)
            row_property_decremented.identifier = '<='
            max_value.append(row_property_decremented)
            if row_property.free_variables():
                max_value_contains_free_variables = True
        elif property_identifier == '<=':
            max_value.append(row_property)
            if row_property.free_variables():
                max_value_contains_free_variables = True
        elif property_identifier == '>':
            row_property_incremented = _increment_property(row_property)
            row_property_incremented.identifier = '>='
            min_value.append(row_property_incremented)
            if row_property.free_variables():
                min_value_contains_free_variables = True
        elif property_identifier == '>=':
            min_value.append(row_property)
            if row_property.free_variables():
                min_value_contains_free_variables = True
        else:
            filters.extend(property_to_lambdas(row_property))

    # if min_value_contains_free_variables or max_value_contains_free_variables:
    #     # turn min values into filters
    #     for prop in min_value:
    #         filters.extend(property_to_lambdas(prop))
    #     min_value = []

    min_value_deserialized: List[str] = list(
        chain(*[represent_property_arguments(prop) for prop in min_value]))
    max_value_deserialized: List[str] = list(
        chain(*[represent_property_arguments(prop) for prop in max_value]))

    for link_row in table.get_rows():
        if link_row.parent == row.var_id and link_row.kind == generate_property_table.Kind.LINK:
            link_strategy = _infer_int_strategy(link_row, table)
            min_value_deserialized.extend(link_strategy.min_value)
            max_value_deserialized.extend(link_strategy.max_value)
            filters.extend(link_strategy.filters)

    return SymbolicIntegerStrategy(var_id=row.var_id,
                                   min_value=min_value_deserialized,
                                   max_value=max_value_deserialized,
                                   filters=filters)
def _infer_set_strategy(
        row: generate_property_table.Row,
        table: generate_property_table.Table) -> SymbolicSetStrategy:
    elements: Optional[SymbolicStrategy] = None
    min_size: List[str] = []
    max_size: List[str] = []
    filters: List[Lambda] = []

    for other_row in table.get_rows():
        if other_row.parent == row.var_id:
            if other_row.kind == generate_property_table.Kind.LINK:
                link_property = other_row.var_id[:-(len(other_row.parent) + 2)]

                for property_identifier, row_property in other_row.properties.items(
                ):
                    if link_property == 'len':
                        if property_identifier == '<':
                            row_property_decremented = _decrement_property(
                                row_property)
                            max_size.extend(
                                represent_property_arguments(
                                    row_property_decremented))
                        elif property_identifier == '<=':
                            max_size.extend(
                                represent_property_arguments(row_property))
                        elif property_identifier == '>':
                            row_property_increment = _increment_property(
                                row_property)
                            min_size.extend(
                                represent_property_arguments(
                                    row_property_increment))
                        elif property_identifier == '>=':
                            min_size.extend(
                                represent_property_arguments(row_property))
                        else:
                            filters.extend(property_to_lambdas(row_property))
                    else:
                        filters.extend(property_to_lambdas(row_property))
            elif other_row.kind == generate_property_table.Kind.UNIVERSAL_QUANTIFIER:
                elements = _infer_strategy(other_row, table)
            else:
                raise NotImplementedError

    if not elements:
        elements_type = typing.get_args(row.type)[0]
        elements = _strategy_from_type_hint(elements_type)

    return SymbolicSetStrategy(var_id=row.var_id,
                               elements=elements,
                               min_size=min_size,
                               max_size=max_size,
                               filters=filters)
def _infer_from_type_strategy(row: generate_property_table.Row, table: generate_property_table.Table) \
        -> SymbolicFromTypeStrategy:
    filters: List[Lambda] = []

    filters.extend([
        lambda_property for row_property in row.properties.values()
        for lambda_property in property_to_lambdas(row_property)
    ])

    for other_row in table.get_rows():
        if typing.get_origin(row.type) in [dict, collections.abc.Mapping] and \
                other_row.kind == generate_property_table.Kind.UNIVERSAL_QUANTIFIER:
            if row.var_id in {
                    e
                    for s in other_row.get_dependencies().values() for e in s
            }:
                for row_property in other_row.properties.values():
                    lambdas = property_to_lambdas(row_property)
                    for l in lambdas:
                        if row.var_id in l.free_variables and other_row.var_id in l.free_variables:
                            l.free_variables.remove(other_row.var_id)
                            l.condition = f"all({l.condition} for {other_row.var_id} in {other_row.parent})"
                    filters.extend(lambdas)
            else:
                elements = _infer_strategy(other_row, table)
                filters_to_delete = []
                for f in elements.filters:
                    if row.var_id in f.free_variables:
                        new_filter = Lambda(
                            condition=
                            f"all({f.condition} for {other_row.var_id} in {other_row.parent})",
                            free_variables=[row.var_id])
                        filters.append(new_filter)
                        filters_to_delete.append(f)
                elements.filters = [
                    f for f in elements.filters if f not in filters_to_delete
                ]
        elif other_row.parent == row.var_id:
            if other_row.kind == generate_property_table.Kind.LINK:
                filters.extend([
                    lambda_property
                    for row_property in other_row.properties.values()
                    for lambda_property in property_to_lambdas(row_property)
                ])
            elif other_row.kind == generate_property_table.Kind.UNIVERSAL_QUANTIFIER:
                if row.var_id in {
                        e
                        for s in other_row.get_dependencies().values()
                        for e in s
                }:
                    for row_property in other_row.properties.values():
                        lambdas = property_to_lambdas(row_property)
                        for l in lambdas:
                            if row.var_id in l.free_variables and other_row.var_id in l.free_variables:
                                l.free_variables.remove(other_row.var_id)
                                l.condition = f"all({l.condition} for {other_row.var_id} in {row.var_id})"
                        filters.extend(lambdas)
                else:
                    elements = _infer_strategy(other_row, table)
                    filters_to_delete = []
                    for f in elements.filters:
                        if row.var_id in f.free_variables:
                            new_filter = Lambda(
                                condition=
                                f"all({f.condition} for {other_row.var_id} in {row.var_id})",
                                free_variables=[row.var_id])
                            filters.append(new_filter)
                            filters_to_delete.append(f)
                    elements.filters = [
                        f for f in elements.filters
                        if f not in filters_to_delete
                    ]
            else:
                raise NotImplementedError

    # Assume that the other variables are local variables in the strategy
    for f in filters:
        f.free_variables = [row.var_id]

    return SymbolicFromTypeStrategy(var_id=row.var_id,
                                    type=row.type,
                                    filters=filters)
def _infer_dictionary_strategy(
        row: generate_property_table.Row,
        table: generate_property_table.Table) -> SymbolicDictionaryStrategy:
    keys: Optional[SymbolicStrategy] = None
    values: Optional[SymbolicStrategy] = None
    min_size: List[str] = []
    max_size: List[str] = []
    filters: List[Lambda] = []

    for other_row in table.get_rows():
        if row.var_id == other_row.var_id:
            pass
        elif other_row.parent == row.var_id:
            if other_row.kind == generate_property_table.Kind.LINK:
                link_property = other_row.var_id[:-(len(other_row.parent) + 2)]

                for property_identifier, row_property in other_row.properties.items(
                ):
                    parents = []
                    parent = other_row.parent
                    while parent:
                        parents.append(parent)
                        parent = table.get_row_by_var_id(parent).parent
                    if any(parent in row_property.free_variables()
                           for parent in parents):
                        filters.extend(property_to_lambdas(row_property))
                    elif link_property == 'len':
                        if property_identifier == '<':
                            row_property_decremented = _decrement_property(
                                row_property)
                            max_size.extend(
                                represent_property_arguments(
                                    row_property_decremented))
                        elif property_identifier == '<=':
                            max_size.extend(
                                represent_property_arguments(row_property))
                        elif property_identifier == '>':
                            row_property_increment = _increment_property(
                                row_property)
                            min_size.extend(
                                represent_property_arguments(
                                    row_property_increment))
                        elif property_identifier == '>=':
                            min_size.extend(
                                represent_property_arguments(row_property))
                        else:
                            filters.extend(property_to_lambdas(row_property))
                    else:
                        filters.extend(property_to_lambdas(row_property))
            elif other_row.kind == generate_property_table.Kind.UNIVERSAL_QUANTIFIER:
                if row.var_id in {
                        e
                        for s in other_row.get_dependencies().values()
                        for e in s
                }:
                    for row_property in other_row.properties.values():
                        lambdas = property_to_lambdas(row_property)
                        for l in lambdas:
                            if row.var_id in l.free_variables and other_row.var_id in l.free_variables:
                                l.free_variables.remove(other_row.var_id)
                                l.condition = f"all({l.condition} for {other_row.var_id} in {row.var_id})"
                        filters.extend(lambdas)
                else:
                    elements = _infer_strategy(other_row, table)
                    filters_to_delete = []
                    for f in elements.filters:
                        if row.var_id in f.free_variables:
                            new_filter = Lambda(
                                condition=
                                f"all({f.condition} for {other_row.var_id} in {row.var_id})",
                                free_variables=[row.var_id])
                            filters.append(new_filter)
                            filters_to_delete.append(f)
                    elements.filters = [
                        f for f in elements.filters
                        if f not in filters_to_delete
                    ]
            else:
                raise NotImplementedError
        elif other_row.parent == f'{row.var_id}.keys()':
            if other_row.kind == generate_property_table.Kind.UNIVERSAL_QUANTIFIER:
                keys = _infer_strategy(other_row, table)
            else:
                raise NotImplementedError
        elif other_row.parent == f'{row.var_id}.values()':
            if other_row.kind == generate_property_table.Kind.UNIVERSAL_QUANTIFIER:
                values = _infer_strategy(other_row, table)
            else:
                raise NotImplementedError

    if not keys:
        keys_type = typing.get_args(row.type)[0]
        keys = _strategy_from_type_hint(keys_type)

    if not values:
        values_type = typing.get_args(row.type)[1]
        values = _strategy_from_type_hint(values_type)

    return SymbolicDictionaryStrategy(var_id=row.var_id,
                                      keys=keys,
                                      values=values,
                                      min_size=min_size,
                                      max_size=max_size,
                                      filters=filters)
def _infer_list_strategy(
        row: generate_property_table.Row,
        table: generate_property_table.Table) -> SymbolicListStrategy:
    elements: Optional[SymbolicStrategy] = None
    min_size: List[str] = []
    max_size: List[str] = []
    unique_by: List[UniqueBy] = []
    unique: bool = False
    filters: List[Lambda] = []

    for property_identifier, row_property in row.properties.items():
        if property_identifier == 'IS_UNIQUE':
            unique = True
        else:
            raise NotImplementedError(
                "'is_unique' is currently the only property applying directly to a list."
            )

    for other_row in table.get_rows():
        if other_row.parent == row.var_id:
            if other_row.kind == generate_property_table.Kind.LINK:
                link_property = other_row.var_id[:-(len(other_row.parent) + 2)]

                for property_identifier, row_property in other_row.properties.items(
                ):
                    parents = []
                    parent = other_row.parent
                    while parent:
                        parents.append(parent)
                        parent = table.get_row_by_var_id(parent).parent
                    if any(parent in row_property.free_variables()
                           for parent in parents):
                        filters.extend(property_to_lambdas(row_property))
                    elif link_property == 'len':
                        if property_identifier == '<':
                            row_property_decremented = _decrement_property(
                                row_property)
                            max_size.extend(
                                represent_property_arguments(
                                    row_property_decremented))
                        elif property_identifier == '<=':
                            max_size.extend(
                                represent_property_arguments(row_property))
                        elif property_identifier == '>':
                            row_property_increment = _increment_property(
                                row_property)
                            min_size.extend(
                                represent_property_arguments(
                                    row_property_increment))
                        elif property_identifier == '>=':
                            min_size.extend(
                                represent_property_arguments(row_property))
                        else:
                            filters.extend(property_to_lambdas(row_property))
                    else:
                        filters.extend(property_to_lambdas(row_property))
            elif other_row.kind == generate_property_table.Kind.UNIVERSAL_QUANTIFIER:
                if row.var_id in {
                        e
                        for s in other_row.get_dependencies().values()
                        for e in s
                }:
                    for row_property in other_row.properties.values():
                        lambdas = property_to_lambdas(row_property)
                        for l in lambdas:
                            if row.var_id in l.free_variables and other_row.var_id in l.free_variables:
                                l.free_variables.remove(other_row.var_id)
                                l.condition = f"all({l.condition} for {other_row.var_id} in {row.var_id})"
                        filters.extend(lambdas)
                else:
                    elements = _infer_strategy(other_row, table)
                    filters_to_delete = []
                    for f in elements.filters:
                        if row.var_id in f.free_variables:
                            new_filter = Lambda(
                                condition=
                                f"all({f.condition} for {other_row.var_id} in {row.var_id})",
                                free_variables=[row.var_id])
                            filters.append(new_filter)
                            filters_to_delete.append(f)
                    elements.filters = [
                        f for f in elements.filters
                        if f not in filters_to_delete
                    ]
            else:
                raise NotImplementedError

    if not elements:
        elements_type = typing.get_args(row.type)[0]
        elements = _strategy_from_type_hint(elements_type)

    return SymbolicListStrategy(var_id=row.var_id,
                                elements=elements,
                                min_size=min_size,
                                max_size=max_size,
                                unique_by=unique_by,
                                unique=False or unique,
                                filters=filters)
def _infer_from_regex_strategy(
        row: generate_property_table.Row,
        table: generate_property_table.Table) -> SymbolicFromRegexStrategy:
    regexps: List[str] = []
    full_match: bool = False
    filters: List[Lambda] = []

    for property_identifier, row_property in row.properties.items():
        if property_identifier == 'isalnum':
            regexps.append(r'^[0-9a-zA-Z]+$')
            full_match = True
        elif property_identifier == 'isalpha':
            regexps.append(r'^[a-zA-Z]+$')
            full_match = True
        elif property_identifier == 'isdigit':
            regexps.append(r'^[0-9]*$')
            full_match = True
        elif property_identifier == 'islower':
            regexps.append(r'^[a-z]$')
            full_match = True
        elif property_identifier == 'isnumeric':
            regexps.append(r'^(-[0-9]*|[0-9]*)$')
            full_match = True
        elif property_identifier == 'isspace':
            regexps.append(r'^\s+$')
            full_match = True
        elif property_identifier == 'isupper':
            regexps.append(r'^[A-Z]+$')
            full_match = True
        elif property_identifier == 'isdecimal':
            regexps.append(r'^\d*\.?\d+$')
            full_match = True
        elif property_identifier in ['re.match', 'regex.match']:
            # re.match(r'..', s), we only want the first argument and we don't want any leading/ending \'
            regexps.extend([
                arg.rsplit(',', 1)[0][1:]
                for arg in represent_property_arguments(row_property)
            ])
        elif property_identifier in ['re.fullmatch', 'regex.fullmatch']:
            # re.match(r'..', s), we only want the first argument and we don't want any leading/ending \'
            regexps.extend([
                arg.rsplit(',', 1)[0][1:]
                for arg in represent_property_arguments(row_property)
            ])
            full_match = True
        elif property_identifier == 'contains' or property_identifier == 'in':
            regexps.extend([
                arg.strip("\'")
                for arg in represent_property_arguments(row_property)
            ])
        elif property_identifier == 'startswith':
            stripped_args = [
                arg.strip('\"')
                for arg in represent_property_arguments(row_property)
            ]
            regexps.extend([f'\"^{arg}\"' for arg in stripped_args])
        elif property_identifier == 'endswith':
            stripped_args = [
                arg.strip('\"')
                for arg in represent_property_arguments(row_property)
            ]
            regexps.extend([f'\".*{arg}$\"' for arg in stripped_args])
            full_match = True
        elif row.kind == generate_property_table.Kind.LINK:
            link_property = row.var_id[:-(len(row.parent) + 2)]
            if link_property == 'len':
                if property_identifier == '<':
                    filters.extend(property_to_lambdas(row_property))
                elif property_identifier == '<=':
                    filters.extend(property_to_lambdas(row_property))
                elif property_identifier == '>':
                    filters.extend(property_to_lambdas(row_property))
                elif property_identifier == '>=':
                    filters.extend(property_to_lambdas(row_property))
                else:
                    raise NotImplementedError
            else:
                raise NotImplementedError
        else:
            filters.extend(property_to_lambdas(row_property))

    for link_row in table.get_rows():
        if link_row.parent == row.var_id and link_row.kind == generate_property_table.Kind.LINK:
            link_strategy = _infer_from_regex_strategy(link_row, table)
            regexps.extend(link_strategy.regexps)
            full_match = full_match or link_strategy.full_match
            filters.extend(link_strategy.filters)

    return SymbolicFromRegexStrategy(var_id=row.var_id,
                                     regexps=regexps,
                                     full_match=full_match,
                                     filters=filters)
def _infer_text_strategy(
        row: generate_property_table.Row,
        table: generate_property_table.Table) -> SymbolicTextStrategy:
    blacklist_categories: List[Set[str]] = []
    whitelist_categories: List[Set[str]] = []
    min_size: List[Union[int, str]] = []
    max_size: List[Union[int, str]] = []
    filters: List[Tuple[str, Set[str]]](row.var_id) = []

    for property_identifier, row_property in row.properties.items():
        if property_identifier == 'isalnum':
            whitelist_categories.append({'Ll', 'Lu', 'Nd'})
        elif property_identifier == 'isalpha':
            whitelist_categories.append({'Ll', 'Lu'})
        elif property_identifier == 'isdigit':
            whitelist_categories.append({'Nd'})
        elif property_identifier == 'islower':
            whitelist_categories.append({'Ll'})
        elif property_identifier == 'isnumeric':
            whitelist_categories.append({'Nd', 'Nl', 'No'})
        elif property_identifier == 'isspace':
            whitelist_categories.append({'Zs'})
        elif property_identifier == 'isupper':
            whitelist_categories.append({'Lu'})
        elif property_identifier == 'isdecimal':
            whitelist_categories.append({'Nd'})
        elif row.kind == generate_property_table.Kind.LINK:
            link_property = row.var_id[:-(len(row.parent) + 2)]
            if link_property == 'len':
                if property_identifier == '<':
                    row_property_decremented = _decrement_property(
                        row_property)
                    max_size.extend(
                        represent_property_arguments(row_property_decremented))
                elif property_identifier == '<=':
                    max_size.extend(represent_property_arguments(row_property))
                elif property_identifier == '>':
                    row_property_increment = _increment_property(row_property)
                    min_size.extend(
                        represent_property_arguments(row_property_increment))
                elif property_identifier == '>=':
                    min_size.extend(represent_property_arguments(row_property))
                elif property_identifier == '==':
                    min_size.extend(represent_property_arguments(row_property))
                    max_size.extend(represent_property_arguments(row_property))
                else:
                    raise NotImplementedError
            else:
                raise NotImplementedError
        else:
            filters.extend(property_to_lambdas(row_property))

    for link_row in table.get_rows():
        if link_row.parent == row.var_id and link_row.kind == generate_property_table.Kind.LINK:
            link_strategy = _infer_text_strategy(link_row, table)
            blacklist_categories.extend(link_strategy.blacklist_categories)
            whitelist_categories.extend(link_strategy.whitelist_categories)
            min_size.extend(link_strategy.min_size)
            max_size.extend(link_strategy.max_size)
            filters.extend(link_strategy.filters)

    return SymbolicTextStrategy(var_id=row.var_id,
                                blacklist_categories=blacklist_categories,
                                whitelist_categories=whitelist_categories,
                                min_size=min_size,
                                max_size=max_size,
                                filters=filters)