Dynamic Structs

Jancy is not the first language to introduce dynamic structs per se – many languages support introspection/reflection facilities, which allow modifying the type system at runtime. Making them, you know, dynamic.

However, dynamic structs in Jancy allow something that’s simply not possible with any other language – and that is, self-description. The lengths of some fields may be non-constanct and expressed as a function of other fields of the same struct (e.g. “length”/”count” fields), by null-termination, etc. This approach is used in many file formats and network protocols (think of TCP/IP variable-length fields).

Example:

dynamic struct FileHdr
{
    uint32_t m_signature;
    uint32_t m_sectionCount;
    Section m_sectionTable [m_sectionCount];
    uint32_t m_version;
    char m_authorName [strlen (m_authorName) + 1];
    char m_authorEmail [strlen (m_authorEmail) + 1];
}

After defining a dynamic struct, just overlap a pointer to it with a buffer and access the fields normally, just the way you would with a regular C struct:

FileHdr const* hdr = (FileHdr const*) buffer;

printf (
    "signature:   %.4s (0x%08x)\n"
    "sections:    %d\n"
    "version:     %d.%d.%d (0x%06x)\n"
    "author:      %s\n"
    "email:       %s\n",
    &hdr.m_signature,
    hdr.m_signature,
    hdr.m_sectionCount,
    (hdr.m_version & 0xff0000) >> 16,
    (hdr.m_version & 0x00ff00) >> 8,
    (hdr.m_version & 0x0000ff),
    hdr.m_version,
    hdr.m_authorName,
    hdr.m_authorEmail
    );

All calculated offsets are cached and then re-used during the next access, so you don’t have to worry much about the performance aspect of dynamic structs.

You can even fill the buffer using a non-const dynamic struct pointer. Just make sure you assign the fields in correct order (usually, top-to-bottom), so that dynamic offsets could be calculated correctly.

To get the actual size of a dynamic struct, you can apply a dynamic sizeof operator:

size_t size = dynamic sizeof (*p);

Restrictions:

Of course, given the fact that the size of such struct is dynamically calculated, there are natural limitations applied:

  • Can’t allocate a dynamic struct, can only have a pointer to it;

  • Pointer subtraction is illegal;

  • Pointer increment is only allowed for +1 delta, e.g.:

    p++;
    

Another thing to consider when using dynamic structs is this. Dynamic offsets are cached for efficiency, as has been said above. However, this cache is a double-edged sword when you need to re-use the same buffer for different data. The cache, of course, knows nothing about that, so it will keep reporting the now-erroneous offsets.

To combat that, be sure to reset the cache with std.resetDynamicLayout before re-using the same buffer with new data:

memcpy (buffer, newData, newDataSize);
std.resetDynamicLayout (buffer);

FileHdr const* hdr = (FileHdr const*) buffer;

// offsets will be re-calculated on access