.. _tutorial-basic_other:

=================
Common Structures
=================

In addition to the basic field types we've already covered, *Caterpillar* offers more
advanced struct types for handling complex data structures. These types can simplify
parsing and packing operations, especially when dealing with constants, compression,
or specialized data handling.

Constants
---------

In many binary formats, constants or "magic bytes" are used to identify the start
of a file or data stream. *Caterpillar* allows you to define and automatically
validate these constants against the parsed data, saving you from manually adding
them in every time.

For instance, a PNG file starts with a known sequence of magic bytes:
:code:`\x89PNG\x0D\x0A\x1A\x0A`. You can define these constants directly in your
struct like so:

.. code-block:: python
    :caption: Starting the *main* PNG struct

    @struct(order=BigEndian) # <-- will be relevant later on
    class PNG:
        magic: b"\x89PNG\x0D\x0A\x1A\x0A"
        # other fields will be defined at the end of this tutorial.


For raw constant values, *Caterpillar* provides the :class:`~caterpillar.py.Const`
struct, which allows you to define constant values that need to be packed or
unpacked.

>>> const = Const(0xbeef, uint32)

Compression
-----------

*Caterpillar* also supports common compression formats such as `zlib`, `lzma`, `bz2`,
and, if the library is installed, `lzo`. This allows you to handle compressed data
within your struct definitions easily.

>>> compressed = ZLibCompressed(100) # length or struct here applicable

Specials
--------

There are several special structs for handling more advanced or less common scenarios.

Computed
~~~~~~~~

The `Computed` struct allows you to define a runtime computed variable that doesn't
actually pack any data. While you could use a :code:`@property` or method to represent
this, :code:`Computed` is useful when you need to calculate a value during the packing
or unpacking process.

You might want to compute the real gamma value for a PNG chunk, based on another field
in the struct:

.. code-block:: python
    :caption: Example implementation of the *gAMA* chunk

    @struct(order=BigEndian)    # <-- same as usual
    class GAMAChunk:
        gamma: uint32
        gamma_value: Computed(this.gamma / 100000)

Pass
~~~~

The :code:`Pass` struct is used when no action should be taken during the packing or unpacking
process. It doesn't affect the data stream in any way.

You can use `Pass` when you simply need to skip over certain parts of the data without modifying them:

>>> @struct
... class Format:
...     foo: Pass  # This won't affect the stream and will store None
...