X_TML_VERSION_MAJOR
#define X_TML_VERSION_MAJOR 2
STDX - A lightweight hierarchical data language optimized for fast parsing and tree traversal.
Part of the STDX General Purpose C Library by marciovmf
License: MIT
https://github.com/marciovmf/stdx
TML (Tree Markup Language) is a minimal indentation-based hierarchical data format designed for:
TML borrows some readability ideas from YAML while intentionally avoiding many of its parsing ambiguities and complexity.
The parser operates in two passes:
The final document is intended primarily for reading/traversal.
To compile the implementation define X_IMPL_TML
in one source file before including this header.
To customize how this module allocates memory, define
X_ARRAY_ALLOC / X_ARRAY_REALLOC / X_ARRAY_FREE
before including this header.
Direct children are linked using:
firstchild + nextsibling
because child nodes are not necessarily contiguous in memory due to depth-first document ordering.
Comments start with '#': # this is a comment
Comments are ignored until the end of the line.
Indentation defines hierarchy. The first indented line defines the indentation unit. Valid values are 2 or 4 spaces.
Tabs are invalid for indentation.
Nodes are declared using:
identifier:
Example:
player:
transform:
Valid identifiers follow standard C variable naming rules:
valid_name:
_another_name:
Invalid:
0_invalid: # identifier can't start with number
Nodes are hierarchical containers. Nodes are NOT values.
Entries use:
key: value
Example:
enabled: true
health: 100
speed: 3.5
Entries are never anonymous.
Anonymous child nodes are declared with '-':
objects:
- name: "tree"
enabled: true
Anonymous nodes still have stable child indices inside their parent scope. The first key:value pair must appear on the same line as '-'.
Strings use double quotes: name: "Player"
Strings support escape sequences: n r t " \
Example:
text: "Hello\nWorld"
Strings may span multiple lines. A string only ends at the matching closing quote.
Example:
text: "This is a
multiline string."
Indentation inside multiline strings is preserved exactly. Comments inside strings are treated as literal text. Internally strings are stored as views (ptr + size) but are also NUL-terminated for easy interoperability with C APIs.
Arrays are homogeneous lists of values declared using comma-separated values:
values: 1, 2, 3
explicit_values: [1, 2, 3]
one_value: [1]
Square brackets are optional, but they are required to distinguish a one-element array from a scalar value. Empty arrays are not supported because the element type is inferred from the first value.
The type of the array is determined by the type of the first element. Supported array types are i64, f64 and string
Valid:
values: 1, 2, 3
points: 1.0, 2.0, 3.0
names: "a", "b", "c"
Invalid:
values: 1, 2.0, 3 # arrays do not support mixed types!
Array elements may span multiple lines. If a line ends with ',' the next line is considered part of the same array. Indentation is ignored while continuing multiline arrays. Example:
vertices:
1.0, 2.0, 3.0,
4.0, 5.0, 6.0,
7.0, 8.0, 9.0
Every node or entry can be uniquely addressed using a dot path.
Example: scene.objects.1.position
Path segments may refer to: named child nodes, child node indices or entry names
Child indices are stable and include BOTH named and anonymous nodes.
Example:
root:
foo:
bar:
- enabled: true
Child indices:
foo -> 0 bar -> 1 anonymous node -> 2
Parsing a document:
TMLParseResult result;
result = tml_parse(source);
if (!result.ok)
{
printf("Parse error at %u:%u : %s\n",
result.line,
result.column,
result.error);
}
Accessing nodes:
TMLNode const* root;
root = tml_root_node_at(result.document, 0);
Accessing entries:
i64 health;
if (tml_node_get_i64(result.document, root, "health", &health))
{
printf("%lld\n", (long long)health);
}
Accessing paths:
f64 speed;
if (tml_path_get_f64(
result.document,
"player.movement.speed",
&speed))
{
...
}
Accessing arrays:
TMLF64Slice vertices;
if (tml_path_get_f64_array(
result.document,
"mesh.vertices",
&vertices))
{
for (u32 i = 0; i < vertices.count; ++i)
{
printf("%f\n", vertices.data[i]);
}
}
Releasing memory:
tml_document_free(result.document);
#define X_TML_VERSION_MAJOR 2
#define X_TML_VERSION_MINOR 1
#define X_TML_VERSION_PATCH 0
#define X_TML_VERSION(X_TML_VERSION_MAJOR *10000+X_TML_VERSION_MINOR *100+X_TML_VERSION_PATCH)
typedef double f64;
typedef struct TMLString{
char const *data;
u32 size;
}TMLString;
typedef enum TMLValueType{
TML_VALUE_BOOL,
TML_VALUE_I64,
TML_VALUE_F64,
TML_VALUE_STRING,
TML_VALUE_ARRAY_I64,
TML_VALUE_ARRAY_F64,
TML_VALUE_ARRAY_STRING
}TMLValueType;
typedef struct TMLArray{
u32 first;
u32 count;
}TMLArray;
typedef struct TMLI64Slice{
i64 const *data;
u32 count;
}TMLI64Slice;
typedef struct TMLF64Slice{
f64 const *data;
u32 count;
}TMLF64Slice;
typedef struct TMLStringSlice{
TMLString const *data;
u32 count;
}TMLStringSlice;
typedef struct TMLEntry{
TMLString name;
TMLValueType type;
u32 parent;
u32 next_entry;
union{u8 boolean;i64 integer;f64 number;TMLString string;TMLArray array;};
}TMLEntry;
typedef struct TMLNode{
TMLString name;
u32 parent;
u32 next_sibling;
u32 first_child;
u32 last_child;
u32 child_count;
u32 first_entry;
u32 last_entry;
u32 entry_count;
}TMLNode;
typedef struct TMLDocument{
void *memory;
u64 memory_size;
TMLNode *nodes;
TMLEntry *entries;
char *string_data;
i64 *array_i64;
f64 *array_f64;
TMLString *array_string;
u32 node_count;
u32 entry_count;
u32 string_size;
u32 array_i64_count;
u32 array_f64_count;
u32 array_string_count;
u32 first_root_node;
u32 last_root_node;
u32 root_node_count;
}TMLDocument;
typedef struct TMLParseResult{
int ok;
char error[256];
u32 line;
u32 column;
TMLDocument *document;
}TMLParseResult;
Release all memory associated with a parsed TML document.
X_TML_API void tml_document_free(TMLDocument *doc);
TMLDocument *docParse a TML document from a UTF-8 source string.
X_TML_API TMLParseResult tml_parse(char const *source);
char const *sourceParse result structure. On success: - result.ok is non-zero - result.document contains the parsed immutable document On failure: - result.ok is 0 - result.error contains a human-readable error message - result.line and result.column identify the error location The returned document must be released with: tml_document_free()
Retrieve a node by global node index.
X_TML_API TMLNode const * tml_node_at(
TMLDocument const *doc,
u32 index
);
TMLDocument const *docu32 indexPointer to the node, or NULL if the index is invalid. Nodes are stored sequentially in document order.
Retrieve an entry by global entry index.
X_TML_API TMLEntry const * tml_entry_at(
TMLDocument const *doc,
u32 index
);
TMLDocument const *docu32 indexPointer to the entry, or NULL if the index is invalid. Entries are stored sequentially in document order.
Retrieve a root node by index.
X_TML_API TMLNode const * tml_root_node_at(
TMLDocument const *doc,
u32 index
);
TMLDocument const *docu32 indexPointer to the root node, or NULL if the index is invalid.
Retrieve a direct child node by index.
X_TML_API TMLNode const * tml_node_child_at(
TMLDocument const *doc,
TMLNode const *node,
u32 index
);
TMLDocument const *docTMLNode const *nodeu32 indexPointer to the child node, or NULL if the index is invalid. Child indices are stable and include both named and anonymous nodes.
Retrieve a direct entry from a node by index.
X_TML_API TMLEntry const * tml_node_entry_at(
TMLDocument const *doc,
TMLNode const *node,
u32 index
);
TMLDocument const *docTMLNode const *nodeu32 indexPointer to the entry, or NULL if the index is invalid.
Find a direct child node by name.
X_TML_API TMLNode const * tml_node_find_child(
TMLDocument const *doc,
TMLNode const *node,
char const *name
);
TMLDocument const *docTMLNode const *nodechar const *namePointer to the matching child node, or NULL if no child matches.
Find a direct entry by name.
X_TML_API TMLEntry const * tml_node_find_entry(
TMLDocument const *doc,
TMLNode const *node,
char const *name
);
TMLDocument const *docTMLNode const *nodechar const *namePointer to the matching entry, or NULL if no entry matches.
Retrieve a boolean value from an entry.
X_TML_API int tml_entry_get_bool(
TMLEntry const *entry,
u8 *out_value
);
Non-zero on success, 0 if the entry is NULL or not a boolean.
Retrieve a signed 64-bit integer value from an entry.
X_TML_API int tml_entry_get_i64(
TMLEntry const *entry,
i64 *out_value
);
Non-zero on success, 0 if the entry is NULL or not an integer.
Retrieve a double-precision floating point value from an entry.
X_TML_API int tml_entry_get_f64(
TMLEntry const *entry,
f64 *out_value
);
Non-zero on success, 0 if the entry is NULL or not a float.
Retrieve a string value from an entry.
X_TML_API int tml_entry_get_string(
TMLEntry const *entry,
TMLString *out_value
);
Non-zero on success, 0 if the entry is NULL or not a string.
Retrieve an i64 array slice from an entry.
X_TML_API int tml_entry_get_i64_array(
TMLDocument const *doc,
TMLEntry const *entry,
TMLI64Slice *out_value
);
TMLDocument const *docTMLEntry const *entryTMLI64Slice *out_valueNon-zero on success, 0 if the entry is NULL or not an i64 array.
Retrieve an f64 array slice from an entry.
X_TML_API int tml_entry_get_f64_array(
TMLDocument const *doc,
TMLEntry const *entry,
TMLF64Slice *out_value
);
TMLDocument const *docTMLEntry const *entryTMLF64Slice *out_valueNon-zero on success, 0 if the entry is NULL or not an f64 array.
Retrieve a string array slice from an entry.
X_TML_API int tml_entry_get_string_array(
TMLDocument const *doc,
TMLEntry const *entry,
TMLStringSlice *out_value
);
TMLDocument const *docTMLEntry const *entryTMLStringSlice *out_valueNon-zero on success, 0 if the entry is NULL or not a string array.
Retrieve a boolean entry value from a node by name.
X_TML_API int tml_node_get_bool(
TMLDocument const *doc,
TMLNode const *node,
char const *name,
u8 *out_value
);
TMLDocument const *docTMLNode const *nodechar const *nameu8 *out_valueNon-zero on success, 0 if the entry does not exist or is not a boolean.
Retrieve a signed 64-bit integer entry value from a node by name.
X_TML_API int tml_node_get_i64(
TMLDocument const *doc,
TMLNode const *node,
char const *name,
i64 *out_value
);
TMLDocument const *docTMLNode const *nodechar const *namei64 *out_valueNon-zero on success, 0 if the entry does not exist or is not an integer.
Retrieve a double-precision floating point entry value from a node by name.
X_TML_API int tml_node_get_f64(
TMLDocument const *doc,
TMLNode const *node,
char const *name,
f64 *out_value
);
TMLDocument const *docTMLNode const *nodechar const *namef64 *out_valueNon-zero on success, 0 if the entry does not exist or is not a float.
Retrieve a string entry value from a node by name.
X_TML_API int tml_node_get_string(
TMLDocument const *doc,
TMLNode const *node,
char const *name,
TMLString *out_value
);
TMLDocument const *docTMLNode const *nodechar const *nameTMLString *out_valueNon-zero on success, 0 if the entry does not exist or is not a string.
Retrieve an i64 array entry from a node by name.
X_TML_API int tml_node_get_i64_array(
TMLDocument const *doc,
TMLNode const *node,
char const *name,
TMLI64Slice *out_value
);
TMLDocument const *docTMLNode const *nodechar const *nameTMLI64Slice *out_valueNon-zero on success, 0 if the entry does not exist or is not an i64 array.
Retrieve an f64 array entry from a node by name.
X_TML_API int tml_node_get_f64_array(
TMLDocument const *doc,
TMLNode const *node,
char const *name,
TMLF64Slice *out_value
);
TMLDocument const *docTMLNode const *nodechar const *nameTMLF64Slice *out_valueNon-zero on success, 0 if the entry does not exist or is not an f64 array.
Retrieve a string array entry from a node by name.
X_TML_API int tml_node_get_string_array(
TMLDocument const *doc,
TMLNode const *node,
char const *name,
TMLStringSlice *out_value
);
TMLDocument const *docTMLNode const *nodechar const *nameTMLStringSlice *out_valueNon-zero on success, 0 if the entry does not exist or is not a string array.
Find a node using a dot path.
X_TML_API TMLNode const * tml_path_find_node(
TMLDocument const *doc,
char const *path
);
TMLDocument const *docchar const *pathPointer to the matching node, or NULL if the path does not resolve. Path segments may refer to: - named child nodes - child node indices Example: scene.objects.1
Find an entry using a dot path.
X_TML_API TMLEntry const * tml_path_find_entry(
TMLDocument const *doc,
char const *path
);
TMLDocument const *docchar const *pathPointer to the matching entry, or NULL if the path does not resolve. The final path segment must refer to an entry name. Example: scene.objects.1.position
Retrieve a boolean value using a dot path.
X_TML_API int tml_path_get_bool(
TMLDocument const *doc,
char const *path,
u8 *out_value
);
TMLDocument const *docchar const *pathu8 *out_valueNon-zero on success, 0 if the path does not resolve or the value is not a boolean.
Retrieve a signed 64-bit integer value using a dot path.
X_TML_API int tml_path_get_i64(
TMLDocument const *doc,
char const *path,
i64 *out_value
);
TMLDocument const *docchar const *pathi64 *out_valueNon-zero on success, 0 if the path does not resolve or the value is not an integer.
Retrieve a double-precision floating point value using a dot path.
X_TML_API int tml_path_get_f64(
TMLDocument const *doc,
char const *path,
f64 *out_value
);
TMLDocument const *docchar const *pathf64 *out_valueNon-zero on success, 0 if the path does not resolve or the value is not a float.
Retrieve a string value using a dot path.
X_TML_API int tml_path_get_string(
TMLDocument const *doc,
char const *path,
TMLString *out_value
);
TMLDocument const *docchar const *pathTMLString *out_valueNon-zero on success, 0 if the path does not resolve or the value is not a string.
Retrieve an i64 array using a dot path.
X_TML_API int tml_path_get_i64_array(
TMLDocument const *doc,
char const *path,
TMLI64Slice *out_value
);
TMLDocument const *docchar const *pathTMLI64Slice *out_valueNon-zero on success, 0 if the path does not resolve or the value is not an i64 array.
Retrieve an f64 array using a dot path.
X_TML_API int tml_path_get_f64_array(
TMLDocument const *doc,
char const *path,
TMLF64Slice *out_value
);
TMLDocument const *docchar const *pathTMLF64Slice *out_valueNon-zero on success, 0 if the path does not resolve or the value is not an f64 array.
Retrieve a string array using a dot path.
X_TML_API int tml_path_get_string_array(
TMLDocument const *doc,
char const *path,
TMLStringSlice *out_value
);
TMLDocument const *docchar const *pathTMLStringSlice *out_valueNon-zero on success, 0 if the path does not resolve or the value is not a string array.
Internal macro for allocating memory. To override how this header allocates memory, define this macro with a different implementation before including this header.
#define X_TML_ALLOC(sz) calloc(1, sz)
szInternal macro for freeing memory. To override how this header frees memory, define this macro with a different implementation before including this header.
#define X_TML_FREE(p) free(p)
p#define TML_INVALID_INDEX((uint32_t)0xFFFFFFFFu)
typedef struct TMLStats{
u32 node_count;
u32 entry_count;
u32 string_size;
u32 array_i64_count;
u32 array_f64_count;
u32 array_string_count;
}TMLStats;
typedef enum TMLParseMode{
TML_PARSE_COUNT,
TML_PARSE_WRITE
}TMLParseMode;
typedef struct TMLScope{
u32 node;
u32 indent;
}TMLScope;
typedef struct TMLParser{
char const *source;
char const *cursor;
char const *line_start;
u32 line;
u32 indent_unit;
TMLParseMode mode;
TMLStats stats;
TMLDocument *doc;
u32 node_cursor;
u32 entry_cursor;
u32 string_cursor;
u32 array_i64_cursor;
u32 array_f64_cursor;
u32 array_string_cursor;
TMLScope scopes[256];
u32 scope_count;
char error[256];
u32 error_line;
u32 error_column;
}TMLParser;
typedef enum TMLScalarKind{
TML_SCALAR_BOOL,
TML_SCALAR_I64,
TML_SCALAR_F64,
TML_SCALAR_STRING
}TMLScalarKind;
typedef struct TMLScalar{
TMLScalarKind kind;
union{u8 boolean;i64 integer;f64 number;TMLString string;};
}TMLScalar;