
-------------------------------------------------------------------------------
What is a decoder?

Decoders are functions which accept some form of rawdata(usually a
bytes object) and convert it to and return a python object associated
with the field being parsed.

Decoders are usually fairly simple, especially if built-ins already exist
to do most of the decoding for you. For more esoteric data types, a decoder
may get a bit lengthy and complex.


Here is the most simple decoder in the library:

def decode_numeric(self, rawdata, desc=None, parent=None, attr_index=None):
    '''
    Converts a bytes object into a python int.
    Decoding is done using struct.unpack

    Returns an int decoded represention of the "rawdata" argument.
    '''
    return unpack(self.enc, rawdata)[0]

It takes a bytes object and runs the struct modules unpack function on it
using the encoding stored in the FieldType instance as the unpack format code.


Here is a decoder that is a bit more complex:

def decode_24bit_numeric(self, rawdata, desc=None,
                         parent=None, attr_index=None):
    '''
    Converts a 24-bit bytes object into a python int.
    Decoding is done using struct.unpack and a manual twos-signed check.

    Returns an int decoded represention of the "rawdata" argument.
    '''
    if self.endian == '<':
        rawint = unpack('<I', rawdata + b'\x00')[0]
    else:
        rawint = unpack('>I', b'\x00' + rawdata)[0]

    # if the int can be signed and IS signed then take care of that
    if rawint & 0x800000 and 'i' in self.enc:
        return rawint - 0x10000000  # 0x10000000 == 0x800000 * 2
    return rawint

This works similarly to decode_numeric, but it assumes the rawdata will be
a 3 char long byte string to which it pads it to 4 so the struct module can
unpack it as a 4 byte unsigned int. If it should be signed and the sign bit
is set, the int is adjusted to make it properly signed.


Because decoders are designed to only be used by a FieldTypes parser function,
the rawdata arguments type can be anything as long as the parser provides
what the decoder is expecting to recieve. Most of the time the rawdata is
a bytes string and the decoder turns it into a float, int, string, or
whatever the data is supposed to be. A current exception to this is bitints,
where the parser instead provides an unsigned int as the rawdata and the
decoder masks, shifts, and returns an int from it.


Here is a simple example of a bitint decoder:

def decode_bit(self, rawdata, desc=None, parent=None, attr_index=None):
    '''
    Decodes a single bit from the given int into an int.
    Returns a 1 if the bit is set, or a 0 if it isnt.
    '''
    # mask and shift the int out of the rawdata
    return (rawdata >> parent.ATTR_OFFS[attr_index]) & 1


-------------------------------------------------------------------------------
Where and why are they used?

Decoders are used exclusively by the parser function of the field they are in.
Decoders exist to separate the task of decoding data into a python object from
the task of actually pulling the data from the data stream. This is because
a parser is usually many lines of code, whereas a decoder is much shorter.

By allowing FieldType instances to have a decoder, generic parsers can be
written that work for many different types of data(integers, strings,
half-floats). This makes it easy to implement FieldTypes for new data types
while requiring less lines of code since a parser is being reused.

Decoders are not always needed however. FieldTypes which describe something
other than data dont use decoders. Any hierarchy FieldTypes(such as Struct,
Container, Array, Switch, and BitStruct) wont use decoders as they dont
represent data. Other times one may simply want their FieldType to be more
optimized and decide to build the decoder into the parser.
These same statements apply to encoders.


-------------------------------------------------------------------------------
What positional and keyword arguments should a decoder
function expect and what are their purposes?

required positional:
    self -------- The FieldType instance whose decoder function is being run.

    rawdata ----- The data which is being decoded into a python object.

keyword:
    desc -------- The descriptor whose fields decoder function is being run.

    parent ------ The Block that the decoded object will be parented to.
                  If this argument is to be considered valid, attr_index must
                  be provided and valid as well.

    attr_index -- The index the decoded object can be found under by doing:
                      parent[attr_index].
                  If this is None, assume the parent argument is not valid.