Blog Dion Format Metadesk Team Gallery FAQ

Metadesk

  1. Language
  2. Library
  3. Getting Started
  4. Examples
    1. Iterating tags and children non-recursively
    2. Checking if a node has certain tags
    3. Extracting node children by index and string

Metadesk is an ergonomic parser library for a simple—yet versatile—plaintext language. The language lets you create simple structures and define their meaning with your own code. The library provides the parser, and helpers for introspection and code generation.

Language

The language defines a syntax for common structures, like hierarchical lists of strings with metatags. While the structures you form have a consistent pattern, the full meaning of your files is not determined by Metadesk. Take the following example of some Metadesk code:

@struct Vec3:
{
  x: F32,
  y: F32,
  z: F32,
}

This may seem like a variation on the C syntax for structs. However, in Metadesk, the above code is parsed as a generic hierarchy of strings, producing an abstract syntax tree that looks like this:

So what's going on here?

In the above diagram, every piece of text you see—Vec3, x, F32, struct—is a Metadesk node. Every node has the same basic structure:

Tags can also have children. Syntactically, they look like tag arguments. Take the following example:

@enum(base_type: U64)
@doc("Foo is an example enum that I am using for an explanation.")
Foo:
{
  Foo_X,
  Foo_Y,
  Foo_Z,
  Foo_W,
}

Once again, this may first appear to be a different syntax for C's enum. But in Metadesk it is just a structure of nodes:

Think of tags as an additional list of children that can be attached to any node.

In this example enum is a tag on Foo and enum has one child: base_type. The base_type node itself has the child: U64. Note that the tag and its children have a unique syntax, but they all share the same structure after the parse.

Metadesk provides a flexible syntax to represent parent-children relationships. The string denoting a parent node must be separated from its children with a :, but there are many legal ways of delimiting the children list:

Foo: { A, B, C }
Foo: { A; B; C; }
Foo: { A B C } // Separators can be omitted.
Foo: ( A B C )
Foo: [ A B C ]
Foo: [ A B C )
Foo: ( A B C ]
Foo: A B C // A separator such as , would end the list of children

It is also possible to have nodes without strings, but with children, in which case you'd omit the Foo:, and just delimit the list:

{ A B C }

Library

So, now that we know how to build structured data with Metadesk... what's the point?

The answer to that question is really "whatever is useful for you". To give a few examples, we've internally used Metadesk for many purposes, including code and data generation. This very website is generated with a tool that uses Metadesk!

The Metadesk library provides a simple Metadesk parser, as well as a number of helpers for introspection and generation using parsed Metadesk structures. It is intended to provide a lightweight way to start working with Metadesk data in your tools.

Getting Started

To get started with Metadesk, go to the GitHub repository and either git clone the repository, or download the ZIP file. It is then recommended that you copy and paste all of the code in the source/ folder into a folder that is local to your program, so that your Metadesk programs will continue working even if the library is updated.

To start using the library, all you need to do is then include the Metadesk files and start using the library's API:

#include "md.h"
#include "md.c"

int main(void)
{
  // now you can call MD_* functions!
}

For more information on using the library, check out the Metadesk Reference.

Examples

Iterating tags and children non-recursively

Provided some MD_Node *node, we can iterate node's children like so using the MD_EachNode helper macro.

for(MD_EachNode(child, node->first_child))
{
  // child is an MD_Node * that we can also introspect on
}

Here is an example of iterating the first code example referenced earlier and printing it out:

int main(void)
{
  MD_String8 example_code = MD_S8Lit("@struct Vec3:\n"
                                     "{\n"
                                     "  x: F32,\n"
                                     "  y: F32,\n"
                                     "  z: F32,\n"
                                     "}\n\n");
  MD_ParseResult parse = MD_ParseWholeString(MD_S8Lit("Generated Test Code"),
                                             example_code);
    
  // Iterate through each top-level node
  for(MD_EachNode(node, parse.node->first_child))
  {
    printf("/ %.*s\n", MD_S8VArg(node->string));

    // Print the name of each of the node's tags
    for(MD_EachNode(tag, node->first_tag))
    {
      printf("|-- Tag %.*s\n", MD_S8VArg(tag->string));
    }

    // Print the name of each of the node's children
    for(MD_EachNode(child, node->first_child))
    {
      printf("|-- Child %.*s\n", MD_S8VArg(child->string));
    }
  }

  return 0;
}

This is the output of the above program:

/ Vec3
|-- Tag struct
|-- Child x
|-- Child y
|-- Child z

Checking if a node has certain tags

Given some MD_Node *node, we can check if node has a tag like so:

if(MD_NodeHasTag(node, MD_S8Lit("my_special_tag")))
{
  // node was tagged with @my_special_tag
}

Extracting node children by index and string

Given some MD_Node *node, if we expect node to have children labeled with certain strings, we can avoid explicitly iterating over them and instead rely on helpers provided by the library:

MD_Node *foo_child    = MD_ChildFromString(node, MD_S8Lit("foo"));
MD_Node *bar_child    = MD_ChildFromString(node, MD_S8Lit("bar"));
MD_Node *baz_child    = MD_ChildFromString(node, MD_S8Lit("baz"));
MD_Node *first_child  = MD_ChildFromIndex(node, 0);
MD_Node *second_child = MD_ChildFromIndex(node, 1);
MD_Node *third_child  = MD_ChildFromIndex(node, 2);