<commit_before>"""
A collection of built-in :code:`Field`'s for creating packets.  This module also contains the building blocks for
creating custom FieldTypes as well.
"""

import ctypes

from calpack.models.utils import typed_property

__all__ = ['Field', 'IntField', 'ArrayField', 'PacketField', 'FlagField']

class Field(object):
    """
    A Super class that all other fields inherit from.  This class is NOT intended for direct use.  Custom Fields MUST
    inherit from this class.

    When creating a custom field you MUST define the :code:`c_type` property with a valid :code:`ctypes` data class.
    """
    c_type = None
    field_name = None

    creation_counter = 0

    bit_len = typed_property('bit_len', int, 16)

    def __init__(self, default_val=None):
        super(Field, self).__init__()
        self.default_val = default_val

        self.creation_counter = Field.creation_counter
        Field.creation_counter += 1

    def __get__(self, instance, cls):
        from calpack.models import Packet
        # If being called from a parent "Packet" class, then we actually want to get the internal field value
        if isinstance(instance, Packet):
            return self.c_to_py(instance.get_c_field(self.field_name))
        return self

    def __set__(self, instance, val):
        from calpack.models import Packet
        if isinstance(instance, Packet):
            c_val = self.py_to_c(val)
            instance.set_c_field(self.field_name, c_val)

    def py_to_c(self, val):
        """
        py_to_c - A function used to convert a python object into a valid ctypes assignable object.  As a default
        this function simply returns :code:`val`.  It's up to the other :code:`Field`'s to define this if further
        formatting is required in order to set the internal structure of the packet.

        :param val: the value the user is attempting to set the packet field to.  This can be any python object.
        """
        return val

    def c_to_py(self, c_field):
        """
        c_to_py - A function used to convert the ctypes object into a python object.  As a default this function
        simply returns :code:`c_field` directly from the ctypes.Structure object.  It's up to the other :code:`Field`'s
        to define this if further formatting is required in order to turn the ctypes value into something user friendly.

        :param c_field: a ctypes object from the packet's internal :code:`ctypes.Structure` object
        """
        return c_field

    def create_field_c_tuple(self):
        """
        create_field_c_tuple - A function used to create the required an field in the :code:`ctypes.Structure._fields_`
        tuple.  This must return a tuple that is acceptable for one of the items in the :code:`_fields_` list of the
        :code:`ctypes.Structure`.

        The first value in the tuple MUST be :code:`self.field_name` as this is used to access the internal c
        structure.
        """
        return (self.field_name, self.c_type)


class IntField(Field):
    """
    An Integer field.  This field can be configured to be signed or unsigned.  It's bit length can also be set,
    however the max bit length for this field is 64.

    :param int bit_len: the length in bits of the integer.  Max value of 64. (default 16)
    :param bool signed: whether to treat the int as an signed integer or unsigned integer (default unsigned)
    :param int default_val: the default value of the field (default 0)
    :raises ValueError: if the :code:`bit_len` is less than or equal to 0 or greater than 64
    """

    signed = typed_property('signed', bool, False)

    # TODO: Implement endianness processing
    little_endian = typed_property('little_endian', bool)

    def __init__(self, bit_len=16, signed=False, default_val=0, little_endian=False):
        super(IntField, self).__init__(default_val)

        if bit_len <= 0 or bit_len > 64:
            raise ValueError("bit_len must be between 1 and 64")

        self.default_val = default_val
        self.bit_len = bit_len
        self.little_endian = little_endian
        self.signed = signed

        if self.signed:
            self.c_type = ctypes.c_int64
        else:
            self.c_type = ctypes.c_uint64

    def py_to_c(self, val):
        if not self.signed and val < 0:
            raise TypeError("Signed valued cannot be set for an unsigned IntField!")
        return val

    def create_field_c_tuple(self):
        return (self.field_name, self.c_type, self.bit_len)


class PacketField(Field):
    """
    A custom Field for handling another packet as a field.

    :param packet_cls: A :code:`calpack.models.Packet` subclass that represents another packet
    """

    packet_cls = None

    def __init__(self, packet_cls):
        super(PacketField, self).__init__()

        self.packet_cls = packet_cls
        self.packet = packet_cls()
        self.bit_len = self.packet_cls.bit_len
        self.c_type = self.packet._Packet__c_struct

    def create_field_c_tuple(self):
        return (self.field_name, self.packet_cls._Packet__c_struct)

    def __setattr__(self, arg, value):
        if self.packet_cls is not None and arg in self.packet_cls.fields_order:
            setattr(self.packet_cls, arg, value)
        else:
            super(PacketField, self).__setattr__(arg, value)

    def py_to_c(self, val):
        if not isinstance(val, self.packet_cls):
            raise TypeError("Must be of type {p}".format(p=type(self.packet_cls)))
        return val.c_pkt


class ArrayField(Field):
    """
    A custom field for handling an array of fields

    :param array_cls: a :code:`calpack.models.Field` subclass **object** that represent the Field the array will be
        filled with.
    :param int array_size: the length of the array.
    """
    def __init__(self, array_cls, array_size, default_val=None):
        super(ArrayField, self).__init__(default_val)
        self.array_cls = array_cls
        self.array_size = array_size
        self.c_type = (self.array_cls.c_type * self.array_size)
        self.bit_len = self.array_cls.bit_len * self.array_size

    def c_to_py(self, c_field):
        return c_field[:]

    def py_to_c(self, val):
        if not isinstance(val, ArrayField) and not isinstance(val, list):
            raise TypeError("Must be of type ArrayField or list")

        return self.c_type(*val)

    def create_field_c_tuple(self):
        return (self.field_name, self.array_cls.c_type * self.array_size)


class FlagField(Field):
    """
    A custom field for handling single bit 'flags'.

    :param bool default_val: the default value of the field (default False)
    """
    c_type = ctypes.c_uint8

    def c_to_py(self, c_field):
        return bool(c_field)

    def py_to_c(self, val):
        if not isinstance(val, FlagField) and not isinstance(val, bool):
            raise TypeError("Must be of type `FlagField` or `bool`")

        return int(val)

    def create_field_c_tuple(self):
        return (self.field_name, self.c_type, 1)<commit_msg>fixed issue with bit_len not being properly set.<commit_after>"""
A collection of built-in :code:`Field`'s for creating packets.  This module also contains the building blocks for
creating custom FieldTypes as well.
"""

import ctypes

from calpack.models.utils import typed_property

__all__ = ['Field', 'IntField', 'ArrayField', 'PacketField', 'FlagField']

class Field(object):
    """
    A Super class that all other fields inherit from.  This class is NOT intended for direct use.  Custom Fields MUST
    inherit from this class.

    When creating a custom field you MUST define the :code:`c_type` property with a valid :code:`ctypes` data class.
    """
    c_type = None
    field_name = None

    creation_counter = 0

    bit_len = typed_property('bit_len', int, 0)

    def __init__(self, default_val=None):
        super(Field, self).__init__()
        self.default_val = default_val

        self.creation_counter = Field.creation_counter
        Field.creation_counter += 1

    def __get__(self, instance, cls):
        from calpack.models import Packet
        # If being called from a parent "Packet" class, then we actually want to get the internal field value
        if isinstance(instance, Packet):
            return self.c_to_py(instance.get_c_field(self.field_name))
        return self

    def __set__(self, instance, val):
        from calpack.models import Packet
        if isinstance(instance, Packet):
            c_val = self.py_to_c(val)
            instance.set_c_field(self.field_name, c_val)

    def py_to_c(self, val):
        """
        py_to_c - A function used to convert a python object into a valid ctypes assignable object.  As a default
        this function simply returns :code:`val`.  It's up to the other :code:`Field`'s to define this if further
        formatting is required in order to set the internal structure of the packet.

        :param val: the value the user is attempting to set the packet field to.  This can be any python object.
        """
        return val

    def c_to_py(self, c_field):
        """
        c_to_py - A function used to convert the ctypes object into a python object.  As a default this function
        simply returns :code:`c_field` directly from the ctypes.Structure object.  It's up to the other :code:`Field`'s
        to define this if further formatting is required in order to turn the ctypes value into something user friendly.

        :param c_field: a ctypes object from the packet's internal :code:`ctypes.Structure` object
        """
        return c_field

    def create_field_c_tuple(self):
        """
        create_field_c_tuple - A function used to create the required an field in the :code:`ctypes.Structure._fields_`
        tuple.  This must return a tuple that is acceptable for one of the items in the :code:`_fields_` list of the
        :code:`ctypes.Structure`.

        The first value in the tuple MUST be :code:`self.field_name` as this is used to access the internal c
        structure.
        """
        return (self.field_name, self.c_type)


class IntField(Field):
    """
    An Integer field.  This field can be configured to be signed or unsigned.  It's bit length can also be set,
    however the max bit length for this field is 64.

    :param int bit_len: the length in bits of the integer.  Max value of 64. (default 16)
    :param bool signed: whether to treat the int as an signed integer or unsigned integer (default unsigned)
    :param int default_val: the default value of the field (default 0)
    :raises ValueError: if the :code:`bit_len` is less than or equal to 0 or greater than 64
    """

    signed = typed_property('signed', bool, False)

    # TODO: Implement endianness processing
    little_endian = typed_property('little_endian', bool)

    def __init__(self, bit_len=16, signed=False, default_val=0, little_endian=False):
        super(IntField, self).__init__(default_val)

        if bit_len <= 0 or bit_len > 64:
            raise ValueError("bit_len must be between 1 and 64")

        self.default_val = default_val
        self.bit_len = bit_len
        self.little_endian = little_endian
        self.signed = signed

        if self.signed:
            self.c_type = ctypes.c_int64
        else:
            self.c_type = ctypes.c_uint64

    def py_to_c(self, val):
        if not self.signed and val < 0:
            raise TypeError("Signed valued cannot be set for an unsigned IntField!")
        return val

    def create_field_c_tuple(self):
        return (self.field_name, self.c_type, self.bit_len)


class PacketField(Field):
    """
    A custom Field for handling another packet as a field.

    :param packet_cls: A :code:`calpack.models.Packet` subclass that represents another packet
    """

    packet_cls = None

    def __init__(self, packet_cls):
        super(PacketField, self).__init__()

        self.packet_cls = packet_cls
        self.packet = packet_cls()
        self.bit_len = self.packet_cls.bit_len
        self.c_type = self.packet._Packet__c_struct

    def create_field_c_tuple(self):
        return (self.field_name, self.packet_cls._Packet__c_struct)

    def __setattr__(self, arg, value):
        if self.packet_cls is not None and arg in self.packet_cls.fields_order:
            setattr(self.packet_cls, arg, value)
        else:
            super(PacketField, self).__setattr__(arg, value)

    def py_to_c(self, val):
        if not isinstance(val, self.packet_cls):
            raise TypeError("Must be of type {p}".format(p=type(self.packet_cls)))
        return val.c_pkt


class ArrayField(Field):
    """
    A custom field for handling an array of fields

    :param array_cls: a :code:`calpack.models.Field` subclass **object** that represent the Field the array will be
        filled with.
    :param int array_size: the length of the array.
    """
    def __init__(self, array_cls, array_size, default_val=None):
        super(ArrayField, self).__init__(default_val)
        self.array_cls = array_cls
        self.array_size = array_size
        self.c_type = (self.array_cls.c_type * self.array_size)
        self.bit_len = self.array_cls.bit_len * self.array_size

    def c_to_py(self, c_field):
        return c_field[:]

    def py_to_c(self, val):
        if not isinstance(val, ArrayField) and not isinstance(val, list):
            raise TypeError("Must be of type ArrayField or list")

        return self.c_type(*val)

    def create_field_c_tuple(self):
        return (self.field_name, self.array_cls.c_type * self.array_size)


class FlagField(Field):
    """
    A custom field for handling single bit 'flags'.

    :param bool default_val: the default value of the field (default False)
    """
    c_type = ctypes.c_uint8

    def __init__(self, default_val=False):
        super(FlagField, self).__init__(default_val)
        self.bit_len = 1

    def c_to_py(self, c_field):
        return bool(c_field)

    def py_to_c(self, val):
        if not isinstance(val, FlagField) and not isinstance(val, bool):
            raise TypeError("Must be of type `FlagField` or `bool`")

        return int(val)

    def create_field_c_tuple(self):
        return (self.field_name, self.c_type, 1)
{'fraction_non_alphanumeric': 0.08240067151650811, 'fraction_numerical': 0.004196978175713486, 'mean_word_length': 2.8166043780032033, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1517175', 'n_tokens_mistral': 4298, 'n_tokens_neox': 4137, 'n_words': 1465}