Skip to content

Introspection and serialization tooling for C

License

Notifications You must be signed in to change notification settings

skippuku/introcity

Repository files navigation

intro/city

The intro/city project consists of tooling for effortless introspection and serialization of C data. It is designed to facilitate experimentation and iteration.

The project can be broken into three different parts:

  • intro is parser which generates a c header with information about the types in your program.
  • introlib is a single-file library that provides helper procedures for using the data generated by intro.
  • city is a file format for typed data serialization which is implemented in introlib.

Features

  • No Friction
    • intro parses C code. No need to learn a new way to define a structure type.
    • No accessors. When you load city data, it is written directly to your structure.
    • With City, old files using an old version of a data structure can be loaded just as easily as current files.
    • City does not need IDs on every struct member to do serialization. It can use the members' names. If you change the name of a member, you can even alias to the old name using an attribute.
  • Simple
    • intro does not depend on any external parsing library. It even implements its own preprocessor.
    • The parser is easily integrated into any build system, and the library is a single-file header that compiles as both C and C++.
    • Headers generated by intro only contain data, no functions. The data does not even need to be initialized in any way.
    • The project is written in plain C99 code. No funny C++ business. This means it can be integrated with other languages as well.
  • Attributes
    • Attributes provide extra information about a piece of data and are defined with macros. They can be used for any variety of purposes, for example the length attribute can be used to define an expression that determines the length of a dynamically sized array for serialization.
    • You can create custom attribute types for your own purposes. This might include information for an interface such as ranges and precision, or alternative defaults for different situations, or anything else you can think of.
    • Attributes aren't just strings you parse yourself. There are a variety of attribute categories including int, float, member, expr, and value (which can be any type in your program). For more information, see doc/ATTRIBUTE.md.

Minimal Example

#define INTRO_IMPL
#include <intro.h>

typedef int32_t s32;
typedef struct {
    s32 demo I(fallback 22);
    char * message I(fallback "empty message");
} SaveData;

#include "interactive_test.c.intro" // generated

int
main() {
    SaveData save;

    bool have_file = intro_load_city_file(&save, ITYPE(SaveData), "save.cty");
    if (!have_file) {
        intro_set_fallbacks(&save, ITYPE(SaveData));
    }

    printf("Save file contents:\n");
    intro_print(&save, ITYPE(SaveData), NULL);

    char new_message [1024];
    printf("Enter a new message: ");
    get_input_line(new_message, sizeof new_message);

    save.message = new_message;
    save.demo++;

    intro_create_city_file("save.cty", &save, ITYPE(SaveData));

    return 0;
}

Output

$ ./interactive_test
Save file contents:
{
    demo: s32 = 22;
    message: *char = "empty message";
}
Enter a new message: Hello there!
$ ./interactive_test
Save file contents:
{
    demo: s32 = 23;
    message: *char = "Hello there!";
}

For more examples, check out the test directory.

Build/Install

make release will build a release version of the parser at build/release/intro.
You may want to run make debug && make test to ensure everything is working correctly.

Linux / MSYS2

sudo make install should set you up so you can invoke "intro" from anywhere.

Integration

To integrate intro/city in your code, you can use the intro parser to generate a C header from a source file, then include the generated header somewhere. You also need to include intro.h. intro can trivially be inserted as a step in the build process. For example a Makefile might look like this:

intro.h must be included before your types are declared.

main: main.c data_types.h.intro intro.h
    $(CC) -o $@ main.c
    
data_types.h.intro: data_types.h
    intro -o $@ data_types.h

More information on using intro/city is at usage.

Disclaimers

  • intro/city is currently in beta. APIs and implementations are subject to change. If you intend to use this seriously, please be careful about updating. If you run into problems or friction that I am not aware of, please create a new issue on github.
  • intro/city currently only supports x86_64. This may change in the future.
  • the intro parser is currently not aware of C++ concepts such as private or methods. Only C99 features are fully supported. This may change in the future. You can simply keep relevant types in a separate file.
  • Security against foreign data is not a priority. Don't use these tools with data from a source you don't trust.

Documentation

Documentation is provided in the doc/ directory. It may not always be complete, but should cover the important things. It may be useful to look at the tests in test/ and intro.h itself.

Credits

The intro parser uses stb_ds.h and stb_sprintf.h from the stb libraries provided by Sean Barrett.