|
| 1 | +# fastoml |
| 2 | + |
| 3 | +A high-performance, single-header TOML parser and serializer written in C. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Single-header library** — drop `fastoml.h` into your project |
| 8 | +- **C99 compatible** with C++ friendly |
| 9 | +- **SIMD accelerated** — optional AVX2, SSE2, and ARM NEON |
| 10 | +- **Complete TOML support** — parse, validate, build, and serialize |
| 11 | +- **Arena allocator** — minimal allocation overhead |
| 12 | +- **Custom allocators** — plug in your own malloc/realloc/free |
| 13 | +- **Detailed error reporting** — line, column, and byte offset on failure |
| 14 | +- **Parser reuse** — reset and parse again without reallocating |
| 15 | +- **Cross-platform** — Windows, Linux, macOS |
| 16 | + |
| 17 | +## Quick Start |
| 18 | + |
| 19 | +Copy `fastoml.h` into your project. In **exactly one** `.c` file, define the implementation: |
| 20 | + |
| 21 | +```c |
| 22 | +#define FASTOML_IMPLEMENTATION |
| 23 | +#include "fastoml.h" |
| 24 | +``` |
| 25 | + |
| 26 | +All other files simply include the header: |
| 27 | + |
| 28 | +```c |
| 29 | +#include "fastoml.h" |
| 30 | +``` |
| 31 | + |
| 32 | +### CMake |
| 33 | + |
| 34 | +```cmake |
| 35 | +add_subdirectory(fastoml) |
| 36 | +target_link_libraries(your_target PRIVATE fastoml::fastoml) |
| 37 | +``` |
| 38 | + |
| 39 | +## Usage |
| 40 | + |
| 41 | +### Parsing a TOML string |
| 42 | + |
| 43 | +```c |
| 44 | +#include <stdio.h> |
| 45 | +#include "fastoml.h" |
| 46 | + |
| 47 | +int main(void) { |
| 48 | + const char* input = |
| 49 | + "[server]\n" |
| 50 | + "host = \"127.0.0.1\"\n" |
| 51 | + "port = 8080\n"; |
| 52 | + |
| 53 | + fastoml_options options; |
| 54 | + fastoml_options_default(&options); |
| 55 | + |
| 56 | + fastoml_parser* parser = fastoml_parser_create(&options); |
| 57 | + const fastoml_document* doc = NULL; |
| 58 | + fastoml_error err = {0}; |
| 59 | + |
| 60 | + fastoml_status st = fastoml_parse(parser, input, strlen(input), &doc, &err); |
| 61 | + if (st != FASTOML_OK) { |
| 62 | + fprintf(stderr, "parse error: %s (line %u, col %u)\n", |
| 63 | + fastoml_status_string(st), err.line, err.column); |
| 64 | + fastoml_parser_destroy(parser); |
| 65 | + return 1; |
| 66 | + } |
| 67 | + |
| 68 | + const fastoml_node* root = fastoml_doc_root(doc); |
| 69 | + const fastoml_node* server = fastoml_table_get_cstr(root, "server"); |
| 70 | + |
| 71 | + const fastoml_node* host_node = fastoml_table_get_cstr(server, "host"); |
| 72 | + fastoml_slice host = {0}; |
| 73 | + fastoml_node_as_slice(host_node, &host); |
| 74 | + printf("host = %.*s\n", (int)host.len, host.ptr); |
| 75 | + |
| 76 | + const fastoml_node* port_node = fastoml_table_get_cstr(server, "port"); |
| 77 | + int64_t port = 0; |
| 78 | + fastoml_node_as_int(port_node, &port); |
| 79 | + printf("port = %lld\n", (long long)port); |
| 80 | + |
| 81 | + fastoml_parser_destroy(parser); |
| 82 | + return 0; |
| 83 | +} |
| 84 | +``` |
| 85 | +
|
| 86 | +### Building and serializing a TOML document |
| 87 | +
|
| 88 | +```c |
| 89 | +#include <stdio.h> |
| 90 | +#include "fastoml.h" |
| 91 | +
|
| 92 | +int main(void) { |
| 93 | + fastoml_builder_options options; |
| 94 | + fastoml_builder_options_default(&options); |
| 95 | + fastoml_builder* b = fastoml_builder_create(&options); |
| 96 | +
|
| 97 | + fastoml_value* root = fastoml_builder_root(b); |
| 98 | + fastoml_value* server = fastoml_builder_new_table(b); |
| 99 | + fastoml_builder_table_set_cstr(root, "server", server); |
| 100 | + fastoml_builder_table_set_cstr(server, "host", |
| 101 | + fastoml_builder_new_string(b, (fastoml_slice){"0.0.0.0", 7})); |
| 102 | + fastoml_builder_table_set_cstr(server, "port", |
| 103 | + fastoml_builder_new_int(b, 3000)); |
| 104 | +
|
| 105 | + // Serialize to buffer |
| 106 | + fastoml_serialize_options ser_opts; |
| 107 | + fastoml_serialize_options_default(&ser_opts); |
| 108 | + ser_opts.flags |= FASTOML_SERIALIZE_FINAL_NEWLINE; |
| 109 | +
|
| 110 | + size_t len = 0; |
| 111 | + fastoml_serialize_to_buffer(fastoml_builder_root(b), &ser_opts, NULL, 0, &len); |
| 112 | +
|
| 113 | + char* buf = malloc(len + 1); |
| 114 | + fastoml_serialize_to_buffer(fastoml_builder_root(b), &ser_opts, buf, len + 1, &len); |
| 115 | + printf("%.*s", (int)len, buf); |
| 116 | +
|
| 117 | + free(buf); |
| 118 | + fastoml_builder_destroy(b); |
| 119 | + return 0; |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +Output: |
| 124 | + |
| 125 | +```toml |
| 126 | +[server] |
| 127 | +host = "0.0.0.0" |
| 128 | +port = 3000 |
| 129 | +``` |
| 130 | + |
| 131 | +## API Overview |
| 132 | + |
| 133 | +### Parser |
| 134 | + |
| 135 | +| Function | Description | |
| 136 | +|---|---| |
| 137 | +| `fastoml_parser_create` | Create a new parser instance | |
| 138 | +| `fastoml_parser_destroy` | Free parser and all parsed documents | |
| 139 | +| `fastoml_parser_reset` | Reset parser for reuse | |
| 140 | +| `fastoml_parse` | Parse a TOML string into a document | |
| 141 | +| `fastoml_validate` | Validate without building a tree | |
| 142 | + |
| 143 | +### Document Access |
| 144 | + |
| 145 | +| Function | Description | |
| 146 | +|---|---| |
| 147 | +| `fastoml_doc_root` | Get the root table node | |
| 148 | +| `fastoml_node_kindof` | Get the type of a node | |
| 149 | +| `fastoml_table_get_cstr` | Look up a key in a table | |
| 150 | +| `fastoml_table_size` | Number of entries in a table | |
| 151 | +| `fastoml_table_key_at` | Get key at index | |
| 152 | +| `fastoml_table_value_at` | Get value at index | |
| 153 | +| `fastoml_array_size` | Number of elements in an array | |
| 154 | +| `fastoml_array_at` | Get element at index | |
| 155 | + |
| 156 | +### Value Extraction |
| 157 | + |
| 158 | +| Function | Description | |
| 159 | +|---|---| |
| 160 | +| `fastoml_node_as_bool` | Extract boolean value | |
| 161 | +| `fastoml_node_as_int` | Extract 64-bit integer value | |
| 162 | +| `fastoml_node_as_float` | Extract double value | |
| 163 | +| `fastoml_node_as_slice` | Extract string / datetime slice | |
| 164 | + |
| 165 | +### Builder & Serializer |
| 166 | + |
| 167 | +| Function | Description | |
| 168 | +|---|---| |
| 169 | +| `fastoml_builder_create` | Create a document builder | |
| 170 | +| `fastoml_builder_new_table` | Create a new table value | |
| 171 | +| `fastoml_builder_new_array` | Create a new array value | |
| 172 | +| `fastoml_builder_new_string` | Create a new string value | |
| 173 | +| `fastoml_builder_new_int` | Create a new integer value | |
| 174 | +| `fastoml_builder_new_float` | Create a new float value | |
| 175 | +| `fastoml_builder_new_bool` | Create a new boolean value | |
| 176 | +| `fastoml_builder_table_set_cstr` | Insert a key-value pair | |
| 177 | +| `fastoml_builder_array_push` | Append an element to an array | |
| 178 | +| `fastoml_serialize_to_buffer` | Serialize to a memory buffer | |
| 179 | +| `fastoml_serialize_to_sink` | Serialize with a custom write callback | |
| 180 | + |
| 181 | +### Parse Options |
| 182 | + |
| 183 | +| Flag | Description | |
| 184 | +|---|---| |
| 185 | +| `FASTOML_PARSE_VALIDATE_ONLY` | Validate syntax without building a tree | |
| 186 | +| `FASTOML_PARSE_DISABLE_SIMD` | Disable SIMD optimizations | |
| 187 | +| `FASTOML_PARSE_TRUST_UTF8` | Skip UTF-8 validation for trusted input | |
| 188 | + |
| 189 | +## Benchmarks |
| 190 | + |
| 191 | +Parse throughput measured with [Google Benchmark](https://github.com/google/benchmark) (10 repetitions, mean values). |
| 192 | + |
| 193 | +**Environment:** 12th Gen Intel, 12 threads, Windows 11, Clang (Release) |
| 194 | + |
| 195 | +| Input | Size | fastoml | toml++ v3.4.0 | toml11 v4.4.0 | |
| 196 | +|---|---|---|---|---| |
| 197 | +| small | 184 B | **92.17 MiB/s** | 39.10 MiB/s | 1.28 MiB/s | |
| 198 | +| medium | 1,544 B | **232.26 MiB/s** | 38.05 MiB/s | 1.63 MiB/s | |
| 199 | +| large | 108,599 B | **242.30 MiB/s** | 32.28 MiB/s | 1.69 MiB/s | |
| 200 | +| invalid | 123 B | **79.16 MiB/s** | 20.15 MiB/s | 1.61 MiB/s | |
| 201 | + |
| 202 | +### Speedup vs. alternatives |
| 203 | + |
| 204 | +| Input | vs toml++ | vs toml11 | |
| 205 | +|---|---|---| |
| 206 | +| small | **2.4x** | **72x** | |
| 207 | +| medium | **6.1x** | **142x** | |
| 208 | +| large | **7.5x** | **143x** | |
| 209 | +| invalid | **3.9x** | **49x** | |
| 210 | + |
| 211 | +> All parsers create and destroy their parser state in every iteration for a fair comparison. I/O is excluded — only the parse call is measured. |
| 212 | +
|
| 213 | +## Building |
| 214 | + |
| 215 | +### Requirements |
| 216 | + |
| 217 | +- CMake 3.20+ |
| 218 | +- C99-compatible compiler (MSVC, GCC, Clang) |
| 219 | + |
| 220 | +### Build the example |
| 221 | + |
| 222 | +```bash |
| 223 | +cmake -S . -B build |
| 224 | +cmake --build build --config Release |
| 225 | +./build/example/fastoml_example |
| 226 | +``` |
| 227 | + |
| 228 | +### Build and run benchmarks |
| 229 | + |
| 230 | +```bash |
| 231 | +cmake -S . -B build -DFASTOML_BENCH=ON |
| 232 | +cmake --build build --config Release |
| 233 | +./build/bench/fastoml_bench_parse |
| 234 | +``` |
| 235 | + |
| 236 | +## License |
| 237 | + |
| 238 | +MIT |
0 commit comments