X_TML_VERSION_MAJOR
#define X_TML_VERSION_MAJOR 1
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
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 1
#define X_TML_VERSION_MINOR 0
#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;