5 Commits
Author SHA1 Message Date
moxiu dc1aa5e5b6 add config support 2026-07-30 13:18:41 +02:00
moxiu 215ed9705e git commit -m fraud 2026-07-30 12:27:22 +02:00
moxiuandGitHub 0461488cf2 add downloading section 2026-07-29 21:20:11 +02:00
moxiu 3c62c2e928 add wireless support 2026-07-29 21:16:35 +02:00
moxiu de20c7002d tf i'm doing 2026-07-29 20:49:47 +02:00
125 changed files with 7426 additions and 14 deletions
+12 -3
View File
@@ -1,10 +1,10 @@
# compiler flags
CC = gcc
CFLAGS = -Wall -Wextra -Iincludes -Isrc
CFLAGS = -Wall -Wextra -Iincludes -Isrc -Iincludes/tomlc17-R260618/src
# source & output
SRC = main.c src/driver.c src/data.c
OBJ = main.o src/driver.o src/data.o
SRC = main.c src/driver.c src/data.c src/config.c src/help.c includes/tomlc17-R260618/src/tomlc17.c
OBJ = main.o src/driver.o src/data.o src/config.o src/help.o includes/tomlc17-R260618/src/tomlc17.o
TARGET = k1ng_driver
LDFLAGS = -lusb-1.0
@@ -25,6 +25,15 @@ src/driver.o: src/driver.c
src/data.o: src/data.c
$(CC) $(CFLAGS) -c src/data.c -o src/data.o
src/config.o: src/config.c
$(CC) $(CFLAGS) -c src/config.c -o src/config.o
src/help.o: src/help.c
$(CC) $(CFLAGS) -c src/help.c -o src/help.o
includes/tomlc17-R260618/src/tomlc17.o: includes/tomlc17-R260618/src/tomlc17.c
$(CC) $(CFLAGS) -c includes/tomlc17-R260618/src/tomlc17.c -o includes/tomlc17-R260618/src/tomlc17.o
# cleanup build artifacts
clean:
rm -f $(OBJ) $(TARGET)
+21
View File
@@ -1,10 +1,23 @@
> [!NOTE]
> work in progress, but works!
> [!IMPORTANT]
> U may need something like this for the driver to work:
```
services.udev.extraRules = ''
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="3554", ATTRS{idProduct}=="f54d", MODE="0666"
'';
```
## K1NG-Driver
a driver for K1NG PRO (4K) mouse from redragon
### Downloading
u can download the driver from [Release's](https://github.com/cfels/K1NG-Driver/releases)
### Compiling
```
@@ -12,6 +25,14 @@ nix-shell --run "make clean && make"
sudo ./k1ng_driver
```
### TODO
- [ ] add config support
- [x] changing sens
- [x] wireless mode support
- [x] wired mode support
- [ ] make a gui for the driver
### License
this project is under [MIT License](https://github.com/cfels/K1NG-Driver/blob/dev/LICENSE)
+8 -2
View File
@@ -5,10 +5,16 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include "libusb-1.0.30/libusb/libusb.h"
#include "tomlc17-R260618/src/tomlc17.h"
// mouse pid
// mouse pids
#define TARGET_VID 0x3554
#define TARGET_PID 0xf54d
#define TARGET_PID_WIRED 0xf54d
#define TARGET_PID_WIRELESS 0xf54f
// mouse pid helper
#define IS_TARGET_PID(pid) ((pid) == TARGET_PID_WIRED || (pid) == TARGET_PID_WIRELESS)
#endif
+49
View File
@@ -0,0 +1,49 @@
name: C/C++ CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
DEBUG: "1"
jobs:
build-ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
cache: false
- name: install toml-test
run: go install github.com/toml-lang/toml-test/v2/cmd/toml-test@latest
- name: make clean all
run: make clean all
- name: make test
run: make test
- name: make install
run: make install prefix=./tmp
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.21' # or whatever version you need
cache: false
- name: install toml-test
run: go install github.com/toml-lang/toml-test/v2/cmd/toml-test@latest
- name: make clean all
run: make clean all
- name: make test
run: make test
- name: make install
run: make install prefix=./tmp
@@ -0,0 +1,58 @@
name: Windows Build and Test
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
DEBUG: "1"
jobs:
build-windows:
runs-on: windows-latest
strategy:
matrix:
include:
- { cc: gcc, cxx: g++ }
- { cc: clang, cxx: clang++ }
steps:
- uses: actions/checkout@v4
- name: Install MinGW and tools
run: |
choco install mingw make -y
refreshenv
- name: Install LLVM (for clang build)
if: matrix.cc == 'clang'
run: choco install llvm -y
- name: Setup Go
uses: actions/setup-go@v5
with:
cache: false
- name: Install toml-test
run: go install github.com/toml-lang/toml-test/cmd/toml-test@latest
- name: Build
shell: cmd
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
run: |
make clean all
- name: Run tests
shell: cmd
run: |
make test
- name: Install to temp directory
shell: cmd
run: |
make install prefix=.\tmp
+7
View File
@@ -0,0 +1,7 @@
*~
*.o
*.d
*.a
*.swp
a.out
t
+321
View File
@@ -0,0 +1,321 @@
# tomlc17 API Reference
## Overview
```c
#include "tomlc17.h"
```
The typical usage pattern:
1. Call `toml_parse()`, `toml_parse_file()`, or `toml_parse_file_ex()` to parse a document.
2. Check `result.ok`. On failure, `result.errmsg` contains a description.
3. Navigate the document tree using `toml_get()` or `toml_seek()`.
4. Call `toml_free()` when done.
**Important:** `toml_free()` must be called on every result returned by a parse or merge
function, even if `result.ok` is false.
All strings and data returned from the API are owned by the `toml_result_t` and remain
valid until `toml_free()` is called on that result.
---
## Types
### `toml_type_t`
```c
typedef enum {
TOML_UNKNOWN = 0,
TOML_STRING,
TOML_INT64,
TOML_FP64,
TOML_BOOLEAN,
TOML_DATE,
TOML_TIME,
TOML_DATETIME,
TOML_DATETIMETZ,
TOML_ARRAY,
TOML_TABLE,
} toml_type_t;
```
The type tag carried by every `toml_datum_t`. A datum with `type == TOML_UNKNOWN`
means "not found" or "invalid".
---
### `toml_datum_t`
A node in the parsed document tree.
```c
struct toml_datum_t {
toml_type_t type;
uint32_t flag; // internal
int lineno; // 1-based source line, 0 when synthesized
int colno; // 1-based source column, 0 when synthesized
const char *source; // source name (e.g. filename), NULL when not provided
union {
const char *s; // TOML_STRING: shorthand for str.ptr
struct {
const char *ptr; // NUL-terminated string
int len; // length excluding the NUL terminator
} str; // TOML_STRING
int64_t int64; // TOML_INT64
double fp64; // TOML_FP64
bool boolean; // TOML_BOOLEAN
struct {
int16_t year, month, day;
int16_t hour, minute, second;
int32_t usec; // microseconds
int16_t tz; // timezone offset in minutes
} ts; // TOML_DATE, TOML_TIME, TOML_DATETIME, TOML_DATETIMETZ
struct {
int32_t size; // number of elements
toml_datum_t *elem; // elem[0..size-1]
} arr; // TOML_ARRAY
struct {
int32_t size; // number of key/value pairs
const char **key; // key[0..size-1] (NUL-terminated strings)
int *len; // len[0..size-1] (key lengths)
toml_datum_t *value; // value[0..size-1]
} tab; // TOML_TABLE
} u;
};
```
**Fields:**
- `type`: The type of this datum (see `toml_type_t`).
- `flag`: Internal use only. Do not read or modify.
- `lineno`: 1-based line number in the source document where this value appeared. Set to 0 for synthesized values.
- `colno`: 1-based column number in the source document where this value appeared. Set to 0 for synthesized values.
- `source`: Name of the document this value came from (e.g. the filename), or
`NULL` if no name was supplied. Copied into the result and preserved across
`toml_merge`, so merged documents retain each entry's origin.
- `u`: Union containing the actual value based on the `type`.
**Accessing values by type:**
| `type` | field(s) to read |
|------------------|------------------------------------------|
| `TOML_STRING` | `u.s` or `u.str.ptr` / `u.str.len` |
| `TOML_INT64` | `u.int64` |
| `TOML_FP64` | `u.fp64` |
| `TOML_BOOLEAN` | `u.boolean` |
| `TOML_DATE` | `u.ts.year`, `.month`, `.day` |
| `TOML_TIME` | `u.ts.hour`, `.minute`, `.second`, `.usec` |
| `TOML_DATETIME` | all `u.ts` fields except `.tz` |
| `TOML_DATETIMETZ`| all `u.ts` fields |
| `TOML_ARRAY` | `u.arr.size`, `u.arr.elem[]` |
| `TOML_TABLE` | `u.tab.size`, `u.tab.key[]`, `u.tab.value[]` |
---
### `toml_result_t`
Returned by every parse function.
```c
struct toml_result_t {
bool ok; // true on success
toml_datum_t toptab; // the top-level table; valid when ok == true
char errmsg[200]; // error description; valid when ok == false
void *__internal; // internal use only; do not access
};
```
---
### `toml_option_t`
Global options. See [`toml_set_option()`](#toml_set_option).
```c
struct toml_option_t {
bool check_utf8; // validate UTF-8; default: false
void *(*mem_realloc)(void *ptr, size_t); // default: realloc()
void (*mem_free)(void *ptr); // default: free()
};
```
---
## Parsing
### `toml_parse`
```c
toml_result_t toml_parse(const char *src, int len);
```
Parse a TOML document from a NUL-terminated string. `len` is the length of `src`
excluding the NUL terminator. The result must be released with `toml_free()`.
---
### `toml_parse_file`
```c
toml_result_t toml_parse_file(FILE *fp);
```
Parse a TOML document from an open file handle. The caller is responsible for
calling `fclose(fp)`. The result must be released with `toml_free()`.
---
### `toml_parse_file_ex`
```c
toml_result_t toml_parse_file_ex(const char *fname);
```
Parse a TOML document from a file path. Opens and closes the file internally.
Every parsed datum's `source` is set to `fname`. The result must be released
with `toml_free()`.
---
### `toml_parse_named`
```c
toml_result_t toml_parse_named(const char *src, int len, const char *name);
```
Like `toml_parse`, but tags every parsed datum's `source` with `name` (copied
into the result; pass `NULL` for no name). The name survives `toml_merge`.
---
### `toml_parse_file_named`
```c
toml_result_t toml_parse_file_named(FILE *fp, const char *name);
```
Like `toml_parse_file`, but tags datums with `name`. `toml_parse_file_ex`
automatically uses the file path as the name.
---
## Releasing
### `toml_free`
```c
void toml_free(toml_result_t result);
```
Release all memory associated with a `toml_result_t`. Must be called on every
result returned by a parse or merge function, regardless of whether parsing
succeeded. All pointers obtained from this result become invalid after this call.
---
## Querying
### `toml_get`
```c
toml_datum_t toml_get(toml_datum_t table, const char *key);
```
Look up a single key in a table. Returns a datum with `type == TOML_UNKNOWN`
if the key is not found or if `table` is not a `TOML_TABLE`.
---
### `toml_seek`
```c
toml_datum_t toml_seek(toml_datum_t table, const char *multipart_key);
```
Look up a dot-separated key path starting from `table`. For example,
`"server.host"` is equivalent to calling `toml_get` twice.
Constraints:
- Keys must not contain escape characters.
- The total length of `multipart_key` must not exceed 255 bytes.
Returns a datum with `type == TOML_UNKNOWN` if any component is not found.
---
## Merging and Comparing
### `toml_merge`
```c
toml_result_t toml_merge(const toml_result_t *r1, const toml_result_t *r2);
```
Produce a new result that is `r1` overridden by `r2`. Merge rules:
- Key in r2 not in r1 → added to result.
- Key in r2 with a different type than r1 → overrides r1.
- Key is an array of tables → r2's entries are appended to r1's.
- Key is a table → r2's sub-keys are recursively merged into r1's.
- Otherwise → r2 overrides r1.
All three results (`r1`, `r2`, and the returned result) must each be freed
with `toml_free()` independently.
---
### `toml_equiv`
```c
bool toml_equiv(const toml_result_t *r1, const toml_result_t *r2);
```
Return `true` if the two results represent identical documents. Table key order
and array element order are both significant.
---
## Options
### `toml_default_option`
```c
toml_option_t toml_default_option(void);
```
Return the current default options. Use this to obtain a baseline before
modifying individual fields.
---
### `toml_set_option`
```c
void toml_set_option(toml_option_t opt);
```
Replace the global options. This affects all subsequent parse calls.
Not thread-safe; call once during program initialization.
**Example — custom allocator:**
```c
toml_option_t opt = toml_default_option();
opt.mem_realloc = my_realloc;
opt.mem_free = my_free;
toml_set_option(opt);
```
---
## Deprecated
### `toml_table_find`
```c
toml_datum_t toml_table_find(toml_datum_t table, const char *key);
```
Alias for `toml_get()`. Use `toml_get()` instead.
+43
View File
@@ -0,0 +1,43 @@
# Bare Key Characters in TOML v1.1
In TOML v1.1, the definition of a **bare key** (unquoted key) has been significantly expanded from the strict ASCII-only restriction of TOML v1.0 to support internationalization.
## 1. The TOML v1.0 Definition (Legacy)
In TOML v1.0, a bare key could only contain:
* ASCII letters (`A-Z`, `a-z`)
* ASCII digits (`0-9`)
* Underscores (`_`)
* Dashes (`-`)
## 2. The TOML v1.1 Expansion
TOML v1.1 allows **non-ASCII Unicode characters** in bare keys. The character ranges are largely aligned with the **XML 1.1 `NameChar`** specification, which covers most letters, digits, and combining marks from scripts worldwide.
A `bare-key-char` in TOML v1.1 is defined as:
* Any character allowed in v1.0 (`[A-Za-z0-9_-]`).
* Any non-ASCII Unicode codepoint from the following specific ranges:
* `U+00B2`, `U+00B3`, `U+00B9` (Superscripts)
* `U+00BC` to `U+00BE` (Fractions)
* `U+00C0` to `U+00D6`
* `U+00D8` to `U+00F6`
* `U+00F8` to `U+037D`
* `U+037F` to `U+1FFF`
* `U+200C` to `U+200D` (Zero-width joiners)
* `U+203F` to `U+2040`
* `U+2070` to `U+218F`
* `U+2460` to `U+24FF`
* `U+2C00` to `U+2FEF`
* `U+3001` to `U+D7FF`
* `U+F900` to `U+FDCF`
* `U+FDF0` to `U+FFFD`
* `U+10000` to `U+EFFFF` (Supplementary Planes)
## Summary Table
| Feature | TOML v1.0 | TOML v1.1 |
| :--- | :--- | :--- |
| **Allowed Characters** | `[A-Za-z0-9_-]` | `[A-Za-z0-9_-]` + Wide Unicode ranges |
| **Examples** | `key = "val"` | `café = "coffee"`, `ñoño = "val"`, `日本語 = "val"` |
| **Prohibited** | `café = "val"` (must use `"café"`) | Whitespace, `.`, `[`, `]`, `#`, `=`, etc. |
## Implementation Status in `tomlc17`
The `tomlc17` project recently updated its scanner in commit `df7ecd4` to support these TOML v1.1 ranges using a new helper function `is_unicode_bare_key_char()`. This allows the parser to correctly handle unquoted internationalized keys.
+70
View File
@@ -0,0 +1,70 @@
# CLAUDE.md
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
- **One thing at a time**: Never combine multiple refactoring, optimization, or feature items in a single edit session. Work on them sequentially: implement one, test it, get verification, commit it, and only then proceed to the next.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
## 5. Sprite Environment
- Do not automatically call `sprite-env checkpoints create` or create checkpoints during the session unless explicitly requested by the user.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2024-2026, CK Tan
https://github.com/cktan/tomlc17
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+61
View File
@@ -0,0 +1,61 @@
.NOTPARALLEL:
prefix ?= /usr/local
# remove trailing /
override prefix := $(prefix:%/=%)
DIRS := src simple test
BUILDDIRS := $(DIRS:%=build-%)
CLEANDIRS := $(DIRS:%=clean-%)
FORMATDIRS := $(DIRS:%=format-%)
TESTDIRS := $(DIRS:%=test-%)
###################################
# Define PCFILE content based on prefix
define PCFILE
prefix=${prefix}
Name: libtomlc17
URL: https://github.com/cktan/tomlc17/
Description: TOML C library in c17.
Version: v1.0
Libs: -L$${prefix}/lib -ltomlc17
Cflags: -I$${prefix}/include
endef
# Make it available to subshells
export PCFILE
#################################
all: $(BUILDDIRS)
$(BUILDDIRS):
$(MAKE) -C $(@:build-%=%)
install: all
install -d $(DESTDIR)${prefix}/include
install -d $(DESTDIR)${prefix}/lib
install -d $(DESTDIR)${prefix}/lib/pkgconfig
install -m 0644 src/tomlc17.h $(DESTDIR)${prefix}/include/
install -m 0644 src/tomlcpp.hpp $(DESTDIR)${prefix}/include/
install -m 0644 src/libtomlc17.a $(DESTDIR)${prefix}/lib/
@echo "$$PCFILE" >> $(DESTDIR)${prefix}/lib/pkgconfig/libtomlc17.pc
test: $(TESTDIRS)
format: $(FORMATDIRS)
clean: $(CLEANDIRS)
$(TESTDIRS):
$(MAKE) -C $(@:test-%=%) test
$(CLEANDIRS):
$(MAKE) -C $(@:clean-%=%) clean
$(FORMATDIRS):
$(MAKE) -C $(@:format-%=%) format
.PHONY: $(DIRS) $(BUILDDIRS) $(TESTDIRS) $(CLEANDIRS) $(FORMATDIRS)
.PHONY: all install test format clean
+69
View File
@@ -0,0 +1,69 @@
# tomlc17 Options
## Setting Options: Custom Allocators
By default, `tomlc17` uses the standard C library `realloc()` and `free()`
functions for memory management. You can override this behavior by setting
custom allocator functions using `toml_set_option()`.
This is useful when you need to:
- Use a custom memory pool or arena allocator
- Track memory allocations for debugging
- Enforce memory constraints in embedded or resource-limited environments
- Integrate with a specialized memory management system
### Example: Using Custom Allocators
```c
#include "tomlc17.h"
#include <stdlib.h>
// Custom realloc function
void* my_realloc(void *ptr, size_t size) {
// Your custom allocation logic here
if (size == 0) {
free(ptr);
return NULL;
}
return realloc(ptr, size);
}
// Custom free function
void my_free(void *ptr) {
// Your custom deallocation logic here
free(ptr);
}
int main() {
// Set custom allocators
toml_option_t opt = toml_default_option();
opt.mem_realloc = my_realloc;
opt.mem_free = my_free;
toml_set_option(opt);
// Now all subsequent parse calls will use your custom allocators
toml_result_t result = toml_parse_file_ex("config.toml");
if (result.ok) {
// Use the parsed data
toml_free(result);
}
return 0;
}
```
### Important Notes
- **Initialization:** Call `toml_set_option()` once during program startup, before
any parsing operations.
- **Thread Safety:** `toml_set_option()` is not thread-safe. Set options only during
initialization in a single-threaded context.
- **Custom Allocator Requirements:** Your custom `mem_realloc` function should behave
like standard `realloc()`:
- When `size > 0`: allocate or resize memory, return pointer or NULL on failure
- When `size == 0`: free memory and return NULL
- Your custom `mem_free` function should handle NULL pointers gracefully
For more details, see the [`toml_set_option()` documentation](API.md#toml_set_option)
in API.md.
+150
View File
@@ -0,0 +1,150 @@
# tomlc17
A lightweight, strictly compliant TOML v1.1 parser for C and C++.
## Overview
`tomlc17` parses TOML documents into an in-memory tree structure for
straightforward navigation. It is optimized for clean integration and
efficient execution, utilizing a single-pass scanner, a dedicated
string memory pool, and safe recursive teardowns.
* **Compliance:** Fully implements TOML v1.1 and passes the standard
`toml-test` validation suite.
* **Compatibility:** Written in C17. Fully compatible with C99 and C++.
* **Modern C++ Support:** Includes dedicated C++20 accessors (see
`README_CXX.md`).
* **Zero-Friction Integration:** Amalgamated design. Simply drop
`tomlc17.h` and `tomlc17.c` into your source tree, or build it as a
library.
## Quick Start
For complete API details, refer to [`API.md`](API.md).
### Example: Parsing & Extraction
Parsing a toml document creates a tree data structure in memory that
reflects the document. Information can be extracted by navigating this
data structure.
```c
/*
* Parse the config file simple.toml:
*
* [server]
* host = "www.example.com"
* port = [8080, 8181, 8282]
*
*/
#include "../src/tomlc17.h"
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
static void error(const char *msg, const char *msg1) {
fprintf(stderr, "ERROR: %s%s\n", msg, msg1 ? msg1 : "");
exit(1);
}
int main() {
// Parse the toml file
toml_result_t result = toml_parse_file_ex("simple.toml");
// Check for parse error
if (!result.ok) {
error(result.errmsg, 0);
}
// Extract values
toml_datum_t host = toml_seek(result.toptab, "server.host");
toml_datum_t port = toml_seek(result.toptab, "server.port");
// Print server.host
if (host.type != TOML_STRING) {
error("missing or invalid 'server.host' property in config", 0);
}
printf("server.host = %s\n", host.u.s);
// Print server.port
if (port.type != TOML_ARRAY) {
error("missing or invalid 'server.port' property in config", 0);
}
printf("server.port = [");
for (int i = 0; i < port.u.arr.size; i++) {
toml_datum_t elem = port.u.arr.elem[i];
if (elem.type != TOML_INT64) {
error("server.port element not an integer", 0);
}
printf("%s%" PRId64, i ? ", " : "", elem.u.int64);
}
printf("]\n");
// Done!
toml_free(result);
return 0;
}
```
## Building
For debug build:
```bash
export DEBUG=1
make
```
For release build:
```bash
unset DEBUG
make
```
## Running tests
We run the official `toml-test` as described
[here](https://github.com/toml-lang/toml-test). Refer to
[this
section](https://github.com/toml-lang/toml-test?tab=readme-ov-file#installation)
for prerequisites to run the tests.
The following command invokes the tests:
```bash
make test
```
As of May 7, 2025, all tests passed for TOML v1.0:
```
toml-test v0001-01-01 [/home/cktan/p/tomlc17/test/stdtest/driver]: using embedded tests
valid tests: 185 passed, 0 failed
invalid tests: 371 passed, 0 failed
```
As of Dec 25, 2025, all tests passed for TOML v1.1:
```
toml-test v0001-01-01 [/home/cktan/p/tomlc17/test/stdtest/driver] [no encoder]
valid tests: 214 passed, 0 failed
encoder tests: no encoder command given
invalid tests: 466 passed, 0 failed
```
## Installing
The install command will copy `tomlc17.h`, `tomlcpp.hpp` and
`libtomlc17.a` to the `$prefix/include` and `$prefix/lib` directories.
```bash
unset DEBUG
make clean install prefix=/usr/local
```
## Options
For information on configuring library options, such as setting custom memory
allocators, see [`OPTIONS.md`](OPTIONS.md).
+73
View File
@@ -0,0 +1,73 @@
# C++20 Accessors in tomlc17
The library includes C++ class and routines in the `toml` namespace. These constructs provide:
- **Safe resource management** - Result class respects RAII rules.
- **Direct subtable access** Retrieves values from nested subtables directly.
- **Safe value access** Uses C++ `std::optional` for error handling, throwing exceptions on invalid access.
- **Time and date support** Represents timestamps as `std::chrono` objects for precise time handling.
- **Array conversion** Converts datum arrays into integer or string vectors for easy manipulation.
## Usage
Here is a simple example:
```c++
/*
* Parse the config file simple.toml:
*
* [server]
* host = "www.example.com"
* port = [8080, 8181, 8282]
*
*/
#include "../src/tomlcpp.hpp"
#include <iostream>
#include <vector>
using std::cout;
static void error(const char *msg) {
fprintf(stderr, "ERROR: %s\n", msg);
exit(1);
}
int main() {
// Parse the toml file
toml::Result result = toml::parse_file_ex("simple.toml");
// Check for parse error
if (!result.ok()) {
error(result.errmsg());
}
// Extract values
std::string host;
std::vector<int64_t> port;
try {
// use the Datum::get() method
host = result.get({"server", "host"})->as_str().value();
} catch (const std::bad_optional_access &ex) {
error("missing or invalid 'server.host' property in config");
}
try {
// use the Datum::seek() method
port = result.seek("server.port")->as_intvec().value();
} catch (const std::bad_optional_access &ex) {
error("missing or invalid 'server.port' property in config");
}
// Print values
cout << "server.host = " << host << "\n";
cout << "server.port = [";
for (size_t i = 0; i < port.size(); i++) {
cout << (i ? ", " : "") << port[i];
}
cout << "]\n";
// Done! No need to call toml_free(). Result::~Result() will handle destruction.
return 0;
}
```
@@ -0,0 +1,3 @@
/simple
/simplecpp
/repro
+59
View File
@@ -0,0 +1,59 @@
EXEC = simple simplecpp
# set up CFLAGS / CXXFLAGS
CFLAGS = -std=c17 -Wmissing-declarations -Wall -Wextra -MMD
ifdef DEBUG
CFLAGS += -O0 -g -fsanitize=address,undefined
else
CFLAGS += -O3 -DNDEBUG
endif
# Add -fpic only for non-Windows platforms
ifneq ($(OS), Windows_NT)
CFLAGS += -fpic
endif
CXXFLAGS = $(subst -std=c17,-std=c++20,$(CFLAGS))
# Test if C++ is available
HAVE_CXX := $(shell command -v $(CXX) 2> /dev/null)
# Test for C++20 support
CXX20_SUPPORT := $(shell echo "int main(){}" | $(CXX) -std=c++20 -x c++ -c - -o /dev/null 2>/dev/null && echo yes || echo no)
all: $(EXEC)
simple: simple.c ../src/libtomlc17.a
$(CC) $(CFLAGS) -o $@ $@.c -L../src -ltomlc17
repro: repro.c ../src/libtomlc17.a
$(CC) $(CFLAGS) -o $@ $@.c -L../src -ltomlc17
simplecpp: simplecpp.cpp ../src/libtomlc17.a
ifndef HAVE_CXX
@echo "INFO: skipping $@ because no $(CXX) compiler found"
else ifeq ($(CXX20_SUPPORT),no)
@echo "INFO: skipping $@ because $(CXX) does not support C++20"
else
$(CXX) $(CXXFLAGS) -o $@ $@.cpp -L ../src -l tomlc17
endif
-include simple.d simplecpp.d
test: all
./simple > /dev/null
[ ! -x ./simplecpp ] || ./simplecpp > /dev/null
clean:
rm -f *.o *.d *.a $(EXEC)
distclean: clean
format:
clang-format -i *.[ch] *.cpp
.PHONY: all clean distclean format
+46
View File
@@ -0,0 +1,46 @@
#include "../src/tomlc17.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
const char *PATH = "/tmp/t.toml";
static void setup() {
const char *text =
"[default]\n"
"\n"
"[wayland_displays.\"$WAYLAND_DISPLAY\"]\n"
"seats = [ \"$XDG_SEAT\" ] \n"
"[[clipboards.Default.mime_type_groups]]\n"
"group = [ \"TEXT\", \"STRING\", \"UTF8_STRING\", \"text/plain\" ]\n"
"xxxx xx xx\n";
(void)text;
FILE *fp = fopen(PATH, "w");
fprintf(fp, "%s", text);
fclose(fp);
}
static void run() {
toml_result_t root = toml_parse_file_ex(PATH);
if (!root.ok) {
fprintf(stderr, "toml_parse_file_ex: %s\n", root.errmsg);
toml_free(root);
exit(-1);
}
toml_datum_t wayland_displays =
toml_seek(root.toptab, "main.wayland_displays");
toml_datum_t clipboards = toml_seek(root.toptab, "main.clipboards");
(void)
toml_free(root);
}
int main() {
setup();
run();
return 0;
}
+62
View File
@@ -0,0 +1,62 @@
/*
* Parse the config file simple.toml:
*
* [server]
* host = "www.example.com"
* port = [8080, 8181, 8282]
*
*/
#include "../src/tomlc17.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
static void error(const char *msg, const char *msg1) {
fprintf(stderr, "ERROR: %s%s\n", msg, msg1 ? msg1 : "");
exit(1);
}
int main() {
// Parse the toml file
toml_result_t result = toml_parse_file_ex("simple.toml");
// Check for parse error
if (!result.ok) {
error(result.errmsg, 0);
}
// Extract values
toml_datum_t host = toml_seek(result.toptab, "server.host");
toml_datum_t port = toml_seek(result.toptab, "server.port");
// Print server.host
if (host.type != TOML_STRING) {
error("missing or invalid 'server.host' property in config", 0);
}
printf("server.host = %s\n", host.u.s);
// Print server.port
if (port.type != TOML_ARRAY) {
error("missing or invalid 'server.port' property in config", 0);
}
printf("server.port = [");
for (int i = 0; i < port.u.arr.size; i++) {
toml_datum_t elem = port.u.arr.elem[i];
if (elem.type != TOML_INT64) {
error("server.port element not an integer", 0);
}
printf("%s%d", i ? ", " : "", (int)elem.u.int64);
}
printf("]\n");
// Negative test
toml_datum_t dummy = toml_seek(result.toptab, "server.dummy");
if (dummy.type != TOML_UNKNOWN) {
printf("Error: dummy element exists\n");
exit(1);
}
// Done!
toml_free(result);
return 0;
}
@@ -0,0 +1,3 @@
[server]
host = "www.example.com"
port = [8080, 8181, 8282]
@@ -0,0 +1,58 @@
/*
* Parse the config file simple.toml:
*
* [server]
* host = "www.example.com"
* port = [8080, 8181, 8282]
*
*/
#include "../src/tomlcpp.hpp"
#include <iostream>
#include <vector>
using std::cout;
static void error(const char *msg) {
fprintf(stderr, "ERROR: %s\n", msg);
exit(1);
}
int main() {
// Parse the toml file
toml::Result result = toml::parse_file_ex("simple.toml");
// Check for parse error
if (!result.ok()) {
error(result.errmsg());
}
// Extract values
std::string host;
std::vector<int64_t> port;
try {
// use the Datum::get() method
host = result.get({"server", "host"})->as_str().value();
} catch (const std::bad_optional_access &ex) {
error("missing or invalid 'server.host' property in config");
}
try {
// use the Datum::seek() method
port = result.seek("server.port")->as_intvec().value();
} catch (const std::bad_optional_access &ex) {
error("missing or invalid 'server.port' property in config");
}
// Print values
cout << "server.host = " << host << "\n";
cout << "server.port = [";
for (size_t i = 0; i < port.size(); i++) {
cout << (i ? ", " : "") << port[i];
}
cout << "]\n";
// Done! No need to call toml_free(). Result::~Result() will handle
// destruction.
return 0;
}
+4
View File
@@ -0,0 +1,4 @@
/scanvalue
/scankey
/parser
/*.so.*
+56
View File
@@ -0,0 +1,56 @@
HFILES := tomlc17.h
CFILES := tomlc17.c
OBJ := $(CFILES:.c=.o)
# set up LIB_SHARED
LIB_VERSION := 1.0
LIB := libtomlc17.a
UNAME := $(shell uname)
ifeq ($(UNAME), Darwin)
LIB_SHARED =
else ifeq ($(OS), Windows_NT)
LIB_SHARED =
else
LIB_SHARED = libtomlc17.so.$(LIB_VERSION)
endif
EXEC =
# set up CFLAGS / CXXFLAGS
CFLAGS := -std=c17 -Wmissing-declarations -Wall -Wextra -MMD
ifdef DEBUG
CFLAGS += -O0 -g -fsanitize=address,undefined
else
CFLAGS += -O3 -DNDEBUG
endif
# Add -fpic only for non-Windows platforms
ifneq ($(OS), Windows_NT)
CFLAGS += -fpic
endif
all: $(EXEC) $(LIB) $(LIB_SHARED)
*.o: $(HFILES)
libtomlc17.a: tomlc17.o
ar -rcs $@ $^
$(LIB_SHARED): tomlc17.o
$(CC) -shared -o $@ $^
-include $(OBJ:%.o=%.d) $(EXEC:%=%.d)
test: all
clean:
rm -f *.o *.d *.a *.so.* $(EXEC)
distclean: clean
format:
clang-format -i *.[ch] *.hpp
.PHONY: all clean distclean format
File diff suppressed because it is too large Load Diff
+277
View File
@@ -0,0 +1,277 @@
/* Copyright (c) 2024-2026, CK Tan.
* https://github.com/cktan/tomlc17/blob/main/LICENSE
*/
/**
* @file tomlc17.h
* @brief A TOML parser for C17.
*
* This library provides a simple and efficient way to parse TOML documents
* in C. It supports standard TOML features and provides an easy-to-use API
* for traversing the parsed data.
*/
#ifndef TOMLC17_H
#define TOMLC17_H
// A crude way to determine version. Manually changed.
#define TOMLC17_RELEASE_AFTER "260618"
/*
* USAGE:
*
* 1. Call toml_parse(), toml_parse_file(), or toml_parse_file_ex()
* 2. Check result.ok
* 3. Use toml_get() or toml_seek() to query and traverse the
* result.toptab
* 4. Call toml_free() to release resources.
*
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#ifdef __cplusplus
#define TOML_EXTERN extern "C"
#else
#define TOML_EXTERN extern
#endif
/**
* @brief Enumeration of TOML data types.
*/
enum toml_type_t {
TOML_UNKNOWN = 0, /**< Unknown or invalid type */
TOML_STRING, /**< String type */
TOML_INT64, /**< 64-bit integer type */
TOML_FP64, /**< 64-bit floating point type */
TOML_BOOLEAN, /**< Boolean type */
TOML_DATE, /**< Local date type */
TOML_TIME, /**< Local time type */
TOML_DATETIME, /**< Local datetime type */
TOML_DATETIMETZ, /**< Offset datetime type */
TOML_ARRAY, /**< Array type */
TOML_TABLE, /**< Table type */
};
typedef enum toml_type_t toml_type_t;
/**
* @brief Represents a single piece of TOML data.
*
* This structure is a node in a tree that represents a TOML document.
* The `u` union contains the actual value based on the `type` field.
*/
typedef struct toml_datum_t toml_datum_t;
struct toml_datum_t {
toml_type_t type; /**< Type of the datum */
uint32_t flag; /**< Internal flag, do not use */
int lineno; /**< 1-based source line number, 0 if synthesized */
int colno; /**< 1-based source column number, 0 if synthesized */
const char *source; /**< Source name (e.g. filename), NULL if not provided.
Owned by the result; valid until toml_free(). */
union {
const char *s; /**< Shorthand for str.ptr */
struct {
const char *ptr; /**< NUL terminated string pointer */
int len; /**< Length of the string excluding the terminating NUL */
} str;
int64_t int64; /**< 64-bit integer value */
double fp64; /**< 64-bit floating point value */
bool boolean; /**< Boolean value */
struct { /**< Date and time components */
int16_t year, month, day;
int16_t hour, minute, second;
int32_t usec;
int16_t tz; /**< Timezone offset in minutes */
} ts;
struct { /**< Array data */
int32_t size; /**< Number of elements in the array */
toml_datum_t *elem; /**< Array of elements */
} arr;
struct { /**< Table data */
int32_t size; /**< Number of keys in the table */
const char **key; /**< Array of keys */
int *len; /**< Array of key lengths */
toml_datum_t *value; /**< Array of values corresponding to keys */
} tab;
} u;
};
/**
* @brief Result of a TOML parsing operation.
*/
typedef struct toml_result_t toml_result_t;
struct toml_result_t {
bool ok; /**< True if parsing was successful */
toml_datum_t toptab; /**< The top-level table (valid if ok is true) */
char errmsg[200]; /**< Error message (valid if ok is false) */
void *__internal; /**< Internal state, do not use */
};
/**
* @brief Parse a TOML document from a string.
*
* @param src A NUL-terminated string containing the TOML document.
* @param len The length of the string (excluding the NUL terminator).
* @return A toml_result_t structure. Must be freed with toml_free().
*
* IMPORTANT: src[] must be a NUL terminated string! The len parameter
* does not include the NUL terminator.
*/
TOML_EXTERN toml_result_t toml_parse(const char *src, int len);
/**
* @brief Parse a TOML document, tagging every datum with a source name.
*
* @param src A NUL-terminated string containing the TOML document.
* @param len The length of the string (excluding the NUL terminator).
* @param name A source name (e.g. filename) copied into the result, or NULL.
* Every parsed datum's `source` is set to this name (or NULL).
* @return A toml_result_t structure. Must be freed with toml_free().
*
* IMPORTANT: src[] must be a NUL terminated string! The len parameter
* does not include the NUL terminator.
*/
TOML_EXTERN toml_result_t toml_parse_named(const char *src, int len,
const char *name);
/**
* @brief Parse a TOML document from a file pointer.
*
* @param fp A pointer to the open file. The caller is responsible for closing
* it.
* @return A toml_result_t structure. Must be freed with toml_free().
*
* IMPORTANT: you are still responsible to fclose(fp).
*/
TOML_EXTERN toml_result_t toml_parse_file(FILE *fp);
/**
* @brief Parse a TOML document from a file pointer, tagging datums with a name.
*
* @param fp A pointer to the open file. The caller is responsible for closing
* it.
* @param name A source name copied into the result, or NULL.
* @return A toml_result_t structure. Must be freed with toml_free().
*
* IMPORTANT: you are still responsible to fclose(fp).
*/
TOML_EXTERN toml_result_t toml_parse_file_named(FILE *fp, const char *name);
/**
* @brief Parse a TOML document from a file path.
*
* @param fname The path to the TOML file.
* @return A toml_result_t structure. Must be freed with toml_free().
*/
TOML_EXTERN toml_result_t toml_parse_file_ex(const char *fname);
/**
* @brief Release resources allocated for a TOML result.
*
* @param result The TOML result to free.
*/
TOML_EXTERN void toml_free(toml_result_t result);
/**
* @brief Find a value for a specific key in a TOML table.
*
* @param table The TOML table to search in.
* @param key The key to look for.
* @return The value associated with the key, or a datum with type TOML_UNKNOWN
* if not found.
*/
TOML_EXTERN toml_datum_t toml_get(toml_datum_t table, const char *key);
/**
* @brief Locate a value using a multipart-key (e.g., "a.b.c").
*
* @param table The TOML table to start the search from.
* @param multipart_key A dot-separated key string. No escape characters
* allowed. Maximum length is 255 bytes.
* @return The value found, or a datum with type TOML_UNKNOWN if not found.
*/
TOML_EXTERN toml_datum_t toml_seek(toml_datum_t table,
const char *multipart_key);
/**
* @brief OBSOLETE: use toml_get() instead.
* Find a key in a toml_table. Return the value of the key if found,
* or a TOML_UNKNOWN otherwise.
*/
static inline toml_datum_t toml_table_find(toml_datum_t table,
const char *key) {
return toml_get(table, key);
}
/**
* @brief Merge two TOML results.
*
* All results (r1, r2, and the returned result) must be freed independently.
*
* @param r1 The base TOML result.
* @param r2 The TOML result containing overrides.
* @return A new toml_result_t representing the merged document.
*
* LOGIC:
* ret = copy of r1
* for each item x in r2:
* if x is not in ret:
* set x in ret
* elif x in ret is NOT of the same type:
* override
* elif x in ret is an array of tables:
* append r2.x to ret.x
* elif x in ret is a table:
* merge r2.x to ret.x
* else:
* override
*/
TOML_EXTERN toml_result_t toml_merge(const toml_result_t *r1,
const toml_result_t *r2);
/**
* @brief Compare two TOML results for equality.
*
* Comparison is sensitive to the order of elements in arrays and tables.
*
* @param r1 The first TOML result.
* @param r2 The second TOML result.
* @return True if they are equivalent, false otherwise.
*/
TOML_EXTERN bool toml_equiv(const toml_result_t *r1, const toml_result_t *r2);
/**
* @brief Global options for the TOML parser.
*/
typedef struct toml_option_t toml_option_t;
struct toml_option_t {
bool check_utf8; /**< If true, check if all characters are valid UTF-8.
Default: false. */
void *(*mem_realloc)(
void *ptr,
size_t size); /**< Custom realloc function. Default: realloc(). */
void (*mem_free)(void *ptr); /**< Custom free function. Default: free(). */
};
/**
* @brief Get the default parser options.
*
* Use this to obtain and initialize a toml_option_t structure before
* customizing it.
*
* @return A toml_option_t with default values.
*/
TOML_EXTERN toml_option_t toml_default_option(void);
/**
* @brief Set the global parser options.
*
* Call this only if you need to override the default behavior.
*
* @param opt The options to set.
*/
TOML_EXTERN void toml_set_option(toml_option_t opt);
#endif // TOMLC17_H
+239
View File
@@ -0,0 +1,239 @@
#pragma once
#if !defined(__cplusplus) || __cplusplus < 202002L
#error "This code requires C++20 or later"
#endif
#include "tomlc17.h"
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace toml {
class Datum : public toml_datum_t {
public:
using datetime = std::chrono::sys_time<std::chrono::microseconds>;
using time = std::chrono::hh_mm_ss<std::chrono::microseconds>;
using date = std::chrono::year_month_day;
Datum(toml_datum_t dat = toml_datum_t{}) : toml_datum_t(dat) {}
bool is_table() const { return type == TOML_TABLE; }
bool is_array() const { return type == TOML_ARRAY; }
// Retrieve a string.
std::optional<std::string_view> as_str() const {
if (type != TOML_STRING)
return std::nullopt;
else
return std::string_view{u.str.ptr, (size_t)u.str.len};
}
// Retrieve an int.
std::optional<std::int64_t> as_int() const {
if (type != TOML_INT64)
return std::nullopt;
else
return u.int64;
}
// Retrieve a float.
std::optional<double> as_real() const {
if (type != TOML_FP64)
return std::nullopt;
else
return u.fp64;
}
// Retrieve a boolean.
std::optional<bool> as_bool() const {
if (type != TOML_BOOLEAN)
return std::nullopt;
else
return !!u.boolean;
}
// Retrieve a date.
std::optional<date> as_date() const {
using namespace std::chrono;
if (type != TOML_DATE)
return std::nullopt;
else
return date{year(u.ts.year), month(u.ts.month), day(u.ts.day)};
}
// Retrieve a time.
std::optional<time> as_time() const {
using namespace std::chrono;
if (type != TOML_TIME)
return std::nullopt;
else
return time{hours(u.ts.hour) + minutes(u.ts.minute) +
seconds(u.ts.second) + microseconds(u.ts.usec)};
}
// Retrieve a datetime.
std::optional<datetime> as_datetime() const {
using namespace std::chrono;
if (type != TOML_DATETIME) {
return std::nullopt;
}
year_month_day ymd{year{u.ts.year}, month{(unsigned)u.ts.month},
day{(unsigned)u.ts.day}};
auto tod = hours{u.ts.hour} + minutes{u.ts.minute} + seconds{u.ts.second} +
microseconds{u.ts.usec};
return datetime{sys_days{ymd} + tod};
}
// Retrieve a datetime and minute offset from UTC.
std::optional<std::pair<datetime, int>> as_datetimetz() const {
using namespace std::chrono;
if (type != TOML_DATETIMETZ) {
return std::nullopt;
}
year_month_day ymd{year{u.ts.year}, month{(unsigned)u.ts.month},
day{(unsigned)u.ts.day}};
auto tod = hours{u.ts.hour} + minutes{u.ts.minute} + seconds{u.ts.second} +
microseconds{u.ts.usec};
return std::pair{datetime{sys_days{ymd} + tod}, u.ts.tz};
}
// Retrieve an array of Datum from this datum.
std::optional<std::vector<Datum>> as_vector() const {
if (type != TOML_ARRAY) {
return std::nullopt;
}
std::vector<Datum> ret;
ret.assign(u.arr.elem, u.arr.elem + u.arr.size);
return ret;
}
// Retrieve an array of strings from this datum.
std::optional<std::vector<std::string_view>> as_strvec() const {
try {
auto vec = as_vector().value();
std::vector<std::string_view> ret;
ret.resize(vec.size());
for (size_t i = 0; i < vec.size(); i++) {
ret[i] = vec[i].as_str().value();
}
return ret;
} catch (const std::bad_optional_access &ex) {
return std::nullopt;
}
}
// Retrieve an array of ints from this datum
std::optional<std::vector<int64_t>> as_intvec() const {
try {
auto vec = as_vector().value();
std::vector<int64_t> ret;
ret.resize(vec.size());
for (size_t i = 0; i < vec.size(); i++) {
ret[i] = vec[i].as_int().value();
}
return ret;
} catch (const std::bad_optional_access &ex) {
return std::nullopt;
}
}
// Retrieve an array of doubles from this datum
std::optional<std::vector<double>> as_realvec() const {
try {
auto vec = as_vector().value();
std::vector<double> ret;
ret.resize(vec.size());
for (size_t i = 0; i < vec.size(); i++) {
ret[i] = vec[i].as_real().value();
}
return ret;
} catch (const std::bad_optional_access &ex) {
return std::nullopt;
}
}
// For tables. Retrieve the value of a composite key from this datum
// (which is a table).
std::optional<Datum> get(std::initializer_list<std::string_view> keys) const {
Datum tab = *this;
Datum value;
for (auto key : keys) {
value = {};
if (tab.type != TOML_TABLE) {
return std::nullopt;
}
for (int i = 0; i < tab.u.tab.size; i++) {
if (key ==
std::string_view{tab.u.tab.key[i], (size_t)tab.u.tab.len[i]}) {
value = tab.u.tab.value[i];
break;
}
}
tab = value;
}
return value;
}
// Retrieve the value of a key
std::optional<Datum> get(std::string_view key) const { return get({key}); }
// Retrieve the value of a multi-part key
std::optional<Datum> seek(const char *multipart_key) const {
auto ret = toml_seek(*this, multipart_key);
if (!ret.type) {
return std::nullopt;
}
return ret;
}
}; // class Datum
class Result : private toml_result_t {
public:
Result(const toml_result_t &result) : toml_result_t(result) {}
~Result() noexcept { toml_free(*this); }
// Disallow copying
Result(const Result &) = delete;
Result &operator=(const Result &) = delete;
// Allow moving
Result(Result &&other) noexcept : toml_result_t() { swap(*this, other); }
Result &operator=(Result &&other) noexcept {
swap(*this, other);
return *this;
}
// Access methods
bool ok() const { return toml_result_t::ok; }
Datum toptab() const { return toml_result_t::toptab; }
const char *errmsg() const { return toml_result_t::errmsg; }
// Shortcuts
std::optional<Datum> get(std::initializer_list<std::string_view> keys) const {
return toptab().get(keys);
}
std::optional<Datum> seek(const char *multipart_key) const {
return toptab().seek(multipart_key);
}
private:
friend void swap(Result &a, Result &b) noexcept {
std::swap(static_cast<toml_result_t &>(a), static_cast<toml_result_t &>(b));
}
};
static inline Result parse_file(FILE *fp) { return toml_parse_file(fp); }
static inline Result parse_file_ex(const char *fname) {
return toml_parse_file_ex(fname);
}
static inline Result parse(const std::string &s) {
return toml_parse(s.data(), s.size());
}
}; // namespace toml
+35
View File
@@ -0,0 +1,35 @@
.NOTPARALLEL:
# disable merge tests for now
DIRS := scankey scanvalue parser merge cpp stdtest source
BUILDDIRS = $(DIRS:%=build-%)
CLEANDIRS = $(DIRS:%=clean-%)
FORMATDIRS = $(DIRS:%=format-%)
TESTDIRS = $(DIRS:%=test-%)
all: $(BUILDDIRS)
$(BUILDDIRS):
$(MAKE) -C $(@:build-%=%)
test: $(TESTDIRS)
format: $(FORMATDIRS)
clean: $(CLEANDIRS)
$(CLEANDIRS):
$(MAKE) -C $(@:clean-%=%) clean
$(FORMATDIRS):
$(MAKE) -C $(@:format-%=%) format
$(TESTDIRS):
$(MAKE) -C $(@:test-%=%) test
.PHONY: $(DIRS) $(BUILDDIRS) $(CLEANDIRS) $(FORMATDIRS)
.PHONY: all install format test
+5
View File
@@ -0,0 +1,5 @@
scankey : test scanner on keys
scanvalue : test scanner on values
parser : test parser
merge : test the toml_merge function
stdtest : the official regression tests
@@ -0,0 +1 @@
/test1
@@ -0,0 +1,49 @@
CFLAGS = -std=c17 -fsanitize=address,undefined -Wmissing-declarations -Wall -Wextra -MMD
EXEC = test1
ifdef DEBUG
CFLAGS += -O0 -g
else
CFLAGS += -O3 -DNDEBUG
endif
CXXFLAGS = $(subst -std=c17,-std=c++20,$(CFLAGS))
# Test for C++20 support
CXX20_SUPPORT := $(shell echo "int main(){}" | $(CXX) -std=c++20 -x c++ -c - -o /dev/null 2>/dev/null && echo yes || echo no)
ifeq ($(CXX20_SUPPORT),yes)
all: $(EXEC)
test1: test1.cpp
$(CXX) $(CXXFLAGS) -o $@ $@.cpp -L../../src -ltomlc17
test: all
@echo
@echo =========================
@echo == cpp test
@echo =========================
./test1
else
all:
@echo "Skipped because C++20 not available"
test:
@echo "Skipped because C++20 not available"
endif
-include test1.d
clean:
rm -f *.o *.d $(EXEC)
distclean: clean
format:
clang-format -i *.cpp
.PHONY: all clean distclean format test
+121
View File
@@ -0,0 +1,121 @@
#include "../../src/tomlcpp.hpp"
#include <cstring>
#include <iostream>
static void failed() {
printf("FAILED\n");
exit(1);
}
#define CHECK(x) \
if (x) \
; \
else \
failed()
using namespace std::chrono;
using std::cout;
using std::endl;
static void test_string() {
printf("test string ...\n");
const char *doc = "title = \"Moby Dick\"";
auto result = toml::parse(doc);
CHECK(result.ok());
auto value = *(*result.toptab().get("title")).as_str();
CHECK(value == "Moby Dick");
}
static void test_int() {
printf("test int ...\n");
const char *doc = "count = 20";
auto result = toml::parse(doc);
CHECK(result.ok());
auto value = *(*result.toptab().get("count")).as_int();
CHECK(value == 20);
}
static void test_float() {
printf("test float ...\n");
const char *doc = "speed = 20.5";
auto result = toml::parse(doc);
CHECK(result.ok());
auto value = *(*result.toptab().get("speed")).as_real();
CHECK(value == 20.5);
}
static void test_boolean() {
printf("test boolean ...\n");
const char *doc = "always = true";
auto result = toml::parse(doc);
CHECK(result.ok());
auto value = *(*result.toptab().get("always")).as_bool();
CHECK(value == true);
}
static void test_date() {
printf("test date ...\n");
const char *doc = "christmas = 2025-12-25";
auto result = toml::parse(doc);
CHECK(result.ok());
auto value = *(*result.toptab().get("christmas")).as_date();
CHECK(value == December / 25 / 2025);
}
static void test_time() {
printf("test time ...\n");
const char *doc = "noon = 12:00:00";
auto result = toml::parse(doc);
CHECK(result.ok());
auto value = *(*result.toptab().get("noon")).as_time();
CHECK(value.hours() == 12h && value.minutes() == 0min &&
value.seconds() == 0s);
}
static void test_datetime() {
printf("test datetime ...\n");
const char *doc = "now = 2025-06-01 11:15:00.123";
auto result = toml::parse(doc);
CHECK(result.ok());
auto value = *(*result.toptab().get("now")).as_datetime();
auto ymd = year_month_day{floor<days>(value)};
CHECK(ymd.year() == year{2025} && ymd.month() == month{6} &&
ymd.day() == day{1});
auto tod = hh_mm_ss<microseconds>{value - sys_days{ymd}};
CHECK(tod.hours() == hours(11) && tod.minutes() == minutes(15) &&
tod.seconds() == seconds(0) &&
tod.subseconds() == microseconds(123000));
}
static void test_datetimetz() {
printf("test datetimetz ...\n");
const char *doc = "now = 2025-06-01 11:15:00.123-05:00";
auto result = toml::parse(doc);
CHECK(result.ok());
auto valuetz = *(*result.toptab().get("now")).as_datetimetz();
auto [value, tzoff] = valuetz;
auto ymd = year_month_day{floor<days>(value)};
CHECK(ymd.year() == year{2025} && ymd.month() == month{6} &&
ymd.day() == day{1});
auto tod = hh_mm_ss<microseconds>{value - sys_days{ymd}};
CHECK(tod.hours() == hours(11) && tod.minutes() == minutes(15) &&
tod.seconds() == seconds(0) &&
tod.subseconds() == microseconds(123000));
CHECK(tzoff == -5 * 60);
}
static void test_array() {
printf("test array ...\n");
const char *doc = "array = [1,2,3]";
auto result = toml::parse(doc);
CHECK(result.ok());
auto value = *(*result.toptab().get("array")).as_intvec();
CHECK(value == (std::vector<int64_t>{1, 2, 3}));
}
int main() {
test_string();
test_int();
test_float();
test_boolean();
test_date();
test_time();
test_datetime();
test_datetimetz();
test_array();
return 0;
}
@@ -0,0 +1 @@
/test1
@@ -0,0 +1,27 @@
CFLAGS := -O0 -g -fsanitize=address,undefined -std=c17 -Wmissing-declarations -Wall -Wextra -MMD
EXEC = test1
all: $(EXEC)
test1: test1.c
$(CC) $(CFLAGS) -o $@ $@.c
test: all
@echo
@echo =========================
@echo == merge test
@echo =========================
./test1
-include test1.d
clean:
rm -f *.o *.d $(EXEC)
distclean: clean
format:
clang-format -i *.[ch]
.PHONY: all clean distclean format test
+200
View File
@@ -0,0 +1,200 @@
#include "../../src/tomlc17.c"
#include <inttypes.h>
static void failed() {
printf("FAILED\n");
exit(1);
}
#define CHECK(x) \
if (x) \
; \
else \
failed()
static void check(const char *doc1, const char *doc2, const char *expected) {
toml_result_t r1 = toml_parse(doc1, strlen(doc1));
toml_result_t r2 = toml_parse(doc2, strlen(doc2));
toml_result_t merged = toml_merge(&r1, &r2);
toml_result_t exp = toml_parse(expected, strlen(expected));
CHECK(toml_equiv(&merged, &exp));
toml_free(r1);
toml_free(r2);
toml_free(merged);
toml_free(exp);
}
// All test cases as separate functions
static void test_simple_merge() {
printf("Running test_simple_merge...\n");
const char *doc1 = "title = \"First\"";
const char *doc2 = "version = \"1.0\"";
const char *expected = "title = \"First\"\n"
"version = \"1.0\"";
check(doc1, doc2, expected);
}
static void test_overwrite_values() {
printf("Running test_overwrite_values...\n");
const char *doc1 = "title = \"First\"\n"
"version = \"0.9\"";
const char *doc2 = "version = \"1.0\"";
const char *expected = "title = \"First\"\n"
"version = \"1.0\"";
check(doc1, doc2, expected);
}
static void test_nested_tables() {
printf("Running test_nested_tables...\n");
const char *doc1 = "[owner]\n"
"name = \"Alice\"\n"
"dob = \"1979-05-27\"";
const char *doc2 = "[owner]\n"
"organization = \"ACME\"";
const char *expected = "[owner]\n"
"name = \"Alice\"\n"
"dob = \"1979-05-27\"\n"
"organization = \"ACME\"";
check(doc1, doc2, expected);
}
static void test_array_merging() {
printf("Running test_array_merging...\n");
const char *doc1 = "dependencies = [\"lib1\", \"lib2\"]";
const char *doc2 = "dependencies = [\"lib3\"]";
const char *expected = "dependencies = [\"lib3\"]";
check(doc1, doc2, expected);
}
static void test_deep_merge() {
printf("Running test_deep_merge...\n");
const char *doc1 = "[database]\n"
"server = \"localhost\"\n"
"ports = [8000, 8001]\n"
"connection_max = 5000";
const char *doc2 = "[database]\n"
"ports = [8001, 8002]\n"
"enabled = true";
const char *expected = "[database]\n"
"server = \"localhost\"\n"
"ports = [8001, 8002]\n"
"connection_max = 5000\n"
"enabled = true";
check(doc1, doc2, expected);
}
static void test_complex_merge() {
printf("Running test_complex_merge...\n");
const char *doc1 = "title = \"TOML Example\"\n"
"\n"
"[owner]\n"
"name = \"Tom Preston-Werner\"\n"
"dob = 1979-05-27T07:32:00Z\n"
"\n"
"[database]\n"
"server = \"192.168.1.1\"\n"
"ports = [8001, 8001, 8002]\n"
"connection_max = 5000\n"
"enabled = true";
const char *doc2 = "title = \"Updated TOML Example\"\n"
"\n"
"[owner]\n"
"organization = \"GitHub\"\n"
"\n"
"[database]\n"
"ports = [9000]\n"
"enabled = false\n"
"\n"
"[clients]\n"
"data = [[\"gamma\", \"delta\"], [1, 2]]";
const char *expected = "title = \"Updated TOML Example\"\n"
"\n"
"[owner]\n"
"name = \"Tom Preston-Werner\"\n"
"dob = 1979-05-27T07:32:00Z\n"
"organization = \"GitHub\"\n"
"\n"
"[database]\n"
"server = \"192.168.1.1\"\n"
"ports = [9000]\n"
"connection_max = 5000\n"
"enabled = false\n"
"\n"
"[clients]\n"
"data = [[\"gamma\", \"delta\"], [1, 2]]";
check(doc1, doc2, expected);
}
static void test_array_of_tables() {
printf("Running test_array_of_tables...\n");
const char *doc1 = "[[products]]\n"
"name = \"Hammer\"\n"
"sku = 738594937";
const char *doc2 = "[[products]]\n"
"color = \"red\"";
const char *expected = "[[products]]\n"
"name = \"Hammer\"\n"
"sku = 738594937\n"
"[[products]]\n"
"color = \"red\"";
check(doc1, doc2, expected);
}
static void test_type_conflicts() {
printf("Running test_type_conflicts...\n");
const char *doc1 = "value = 42";
const char *doc2 = "value = \"forty-two\"";
const char *expected = "value = \"forty-two\"";
check(doc1, doc2, expected);
}
static void test_empty_documents() {
printf("Running test_empty_documents...\n");
check("", "a = 1", "a = 1");
check("a = 1", "", "a = 1");
check("", "", "");
}
// Merged result must own its keys: tomlc17 requires each result be freed
// independently, so reading the merged tree after freeing the inputs must
// not touch the freed input pools.
static void test_keys_outlive_inputs() {
printf("Running test_keys_outlive_inputs...\n");
const char *doc1 = "[owner]\n"
"name = \"Alice\"\n";
const char *doc2 = "[owner]\n"
"organization = \"ACME\"\n";
toml_result_t r1 = toml_parse(doc1, strlen(doc1));
toml_result_t r2 = toml_parse(doc2, strlen(doc2));
toml_result_t merged = toml_merge(&r1, &r2);
CHECK(merged.ok);
toml_free(r1);
toml_free(r2);
toml_datum_t owner = toml_get(merged.toptab, "owner");
toml_datum_t name = toml_get(owner, "name");
toml_datum_t org = toml_get(owner, "organization");
CHECK(name.type == TOML_STRING && 0 == strcmp(name.u.str.ptr, "Alice"));
CHECK(org.type == TOML_STRING && 0 == strcmp(org.u.str.ptr, "ACME"));
toml_free(merged);
}
int main() {
test_simple_merge();
test_overwrite_values();
test_nested_tables();
test_array_merging();
test_deep_merge();
test_complex_merge();
test_array_of_tables();
test_type_conflicts();
test_empty_documents();
test_keys_outlive_inputs();
printf("All tests completed.\n");
return 0;
}
@@ -0,0 +1,2 @@
/driver
/out
@@ -0,0 +1,21 @@
CFLAGS := -O0 -g -fsanitize=address,undefined -std=c17 -Wmissing-declarations -Wall -Wextra -MMD
all: driver
driver: parser.c
$(CC) $(CFLAGS) -o $@ parser.c
test: all
bash run.sh
clean:
rm -f *.o *.d driver
distclean: clean
format:
clang-format -i *.[ch]
-include driver.d
.PHONY: all clean distclean format test
@@ -0,0 +1,7 @@
# This is a TOML document
"title" = "TOML Example"
name = "Aero"
weight = 40
species = "Beagle"
@@ -0,0 +1,10 @@
str1 = """
Roses are red
Violets are blue"""
# On a Unix system, the above multi-line string will most likely be the same as:
str2 = "Roses are red\nViolets are blue"
# On a Windows system, it will most likely be equivalent to:
str3 = "Roses are red\r\nViolets are blue"
@@ -0,0 +1,34 @@
# Configuration file
[main]
wayland_displays = [ "$WAYLAND_DISPLAY" ]
clipboards = [ "Default" ]
[default]
connection_timeout = 500
data_timeout = 500
max_entries = 100
max_entries_memory = 10
[wayland_displays."$WAYLAND_DISPLAY"]
connection_timeout = 500
data_timeout = 500
seats = [ "$XDG_SEAT" ]
[wayland_displays."$WAYLAND_DISPLAY"."$XDG_SEAT"]
clipboard = "Default"
regular = true
primary = false
[clipboards.Default]
max_entries = 10
max_entries_memory = 5
allowed_mime_types = [ "text/*", "image/*" ]
[[clipboards.Default.mime_type_groups]]
mime_type = "text/plain;charset=utf-8"
group = [ "TEXT", "STRING", "UTF8_STRING", "text/plain" ]
@@ -0,0 +1,7 @@
str4 = """Here are two quotation marks: "". Simple enough."""
# str5 = """Here are three quotation marks: """.""" # INVALID
str5 = """Here are three quotation marks: ""\"."""
str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\"."""
# "This," she said, "is just a pointless statement."
str7 = """"This," she said, "is just a pointless statement.""""
@@ -0,0 +1,5 @@
# What you see is what you get.
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Tom "Dubs" Preston-Werner'
regex = '<\i\c*\s*>'
@@ -0,0 +1,7 @@
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''
@@ -0,0 +1,8 @@
quot15 = '''Here are fifteen quotation marks: """""""""""""""'''
# apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID
apos15 = "Here are fifteen apostrophes: '''''''''''''''"
# 'That,' she said, 'is still pointless.'
str = ''''That,' she said, 'is still pointless.''''
@@ -0,0 +1,15 @@
# The following strings are byte-for-byte equivalent:
str1 = "abc"
str2 = """
a\
b\
c"""
str3 = """\
a\
b\
c\
"""
@@ -0,0 +1,17 @@
# all results are the same
str1 = "abc"
str2 = """
a\
b\
c"""
str3 = """\
a\
b\
c\
"""
@@ -0,0 +1,9 @@
# line-ending backslash with whitespace "geeee\ "
# result should be "heeee\ngeeee"
str = """
heeee
geeee\
"""
@@ -0,0 +1,16 @@
# The following strings are byte-for-byte equivalent:
str1 = "The quick brown fox jumps over the lazy dog."
str2 = """
The quick brown \
\
fox jumps over \
the lazy dog."""
str3 = """\
The quick brown \
fox jumps over \
the lazy dog.\
"""
@@ -0,0 +1,6 @@
arr = [
{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},
{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},
{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},{x=1},
{x=1}
]
@@ -0,0 +1,4 @@
key = "value"
bare_key = "value"
bare-key = "value"
1234 = "value"
@@ -0,0 +1,2 @@
# seconds are optional in v1.1
dt = 2025-12-25 15:30
@@ -0,0 +1 @@
str = "I'm a string. \"You can quote me\". Name\tJos\xE9\nLocation\tSF."
@@ -0,0 +1,10 @@
# RECOMMENDED
apple.type = "fruit"
apple.skin = "thin"
apple.color = "red"
orange.type = "fruit"
orange.skin = "thick"
orange.color = "orange"
@@ -0,0 +1 @@
3.14159 = "pi"
@@ -0,0 +1,20 @@
int1 = +99
int2 = 42
int3 = 0
int4 = -17
int5 = 1_000
int6 = 5_349_221
int7 = 53_49_221 # Indian number system grouping
int8 = 1_2_3_4_5 # VALID but discouraged
# hexadecimal with prefix `0x`
hex1 = 0xDEADBEEF
hex2 = 0xdeadbeef
hex3 = 0xdead_beef
# octal with prefix `0o`
oct1 = 0o01234567
oct2 = 0o755 # useful for Unix file permissions
# binary with prefix `0b`
bin1 = 0b11010110
@@ -0,0 +1,24 @@
# fractional
flt1 = +1.0
flt2 = 3.1415
flt3 = -0.01
# exponent
flt4 = 5e+22
flt5 = 1e06
flt6 = -2E-2
# both
flt7 = 6.626e-34
flt8 = 224_617.445_991_228
# infinity
sf1 = inf # positive infinity
sf2 = +inf # positive infinity
sf3 = -inf # negative infinity
# not a number
sf4 = nan # actual sNaN/qNaN encoding is implementation-specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation-specific
@@ -0,0 +1,2 @@
bool1 = true
bool2 = false
@@ -0,0 +1,11 @@
odt1 = 1979-05-27T07:32:00Z
odt2 = 1979-05-27T00:32:00-07:00
odt3 = 1979-05-27T00:32:00.999999-07:00
odt4 = 1979-05-27 07:32:00Z
ldt1 = 1979-05-27T07:32:00
ldt2 = 1979-05-27T00:32:00.999999
ld1 = 1979-05-27
lt1 = 07:32:00
lt2 = 00:32:00.999999
lower = 1987-07-05t17:45:00z
@@ -0,0 +1,13 @@
integers = [ 1, 2, 3 ]
colors = [ "red", "yellow", "green" ]
nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ]
nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
string_array = [ "all", 'strings', """are the same""", '''type''' ]
# Mixed-type arrays are allowed
numbers = [ 0.1, 0.2, 0.5, 1.0, 2, 5 ]
contributors = [
"Foo Bar <foo@example.com>",
{ name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" }
]
@@ -0,0 +1,8 @@
integers2 = [
1, 2, 3
]
integers3 = [
1,
2, # this is ok
]
@@ -0,0 +1,102 @@
a = [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
100]
@@ -0,0 +1,9 @@
integers2 = [
1, 2, 3
]
integers3 = [
1,
2, # this is ok
]
@@ -0,0 +1,3 @@
[fruit]
[fruit]
@@ -0,0 +1,5 @@
# 30-left-brackets is okay
key1 = [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1,2]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
# 31-left-brackets causes stackoverflow
key2 = [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1,2]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
@@ -0,0 +1,2 @@
# large month integer overflows
date = 2026-10000000000-01
@@ -0,0 +1,11 @@
[table-1]
key1 = "some string"
key2 = 123
[table-2]
key1 = "another string"
key2 = 456
[dog."tater.man"]
type.name = "pug"
@@ -0,0 +1,6 @@
[a.b.c] # this is best practice
[ d.e.f ] # same as [d.e.f]
[ g . h . i ] # same as [g.h.i]
[ j . "ʞ" . 'l' ] # same as [j."ʞ".'l']
@@ -0,0 +1,11 @@
# [x] you
# [x.y] don't
# [x.y.z] need these
[x.y.z.w] # for this to work
fruit = "apple"
[x] # defining a super-table afterward is ok
country = "usa"
@@ -0,0 +1,9 @@
# Top-level table begins.
name = "Fido"
breed = "pug"
# Top-level table ends.
[owner]
name = "Regina Dogman"
member_since = 1999-08-04
@@ -0,0 +1,4 @@
name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
animal = { type.name = "pug" }
@@ -0,0 +1,11 @@
[[products]]
name = "Hammer"
sku = 738594937
[[products]] # empty table within the array
[[products]]
name = "Nail"
sku = 284758393
color = "gray"
@@ -0,0 +1,100 @@
a0 = 0
a1 = 1
a2 = 2
a3 = 3
a4 = 4
a5 = 5
a6 = 6
a7 = 7
a8 = 8
a9 = 9
a10 = 10
a11 = 11
a12 = 12
a13 = 13
a14 = 14
a15 = 15
a16 = 16
a17 = 17
a18 = 18
a19 = 19
a20 = 20
a21 = 21
a22 = 22
a23 = 23
a24 = 24
a25 = 25
a26 = 26
a27 = 27
a28 = 28
a29 = 29
a30 = 30
a31 = 31
a32 = 32
a33 = 33
a34 = 34
a35 = 35
a36 = 36
a37 = 37
a38 = 38
a39 = 39
a40 = 40
a41 = 41
a42 = 42
a43 = 43
a44 = 44
a45 = 45
a46 = 46
a47 = 47
a48 = 48
a49 = 49
a50 = 50
a51 = 51
a52 = 52
a53 = 53
a54 = 54
a55 = 55
a56 = 56
a57 = 57
a58 = 58
a59 = 59
a60 = 60
a61 = 61
a62 = 62
a63 = 63
a64 = 64
a65 = 65
a66 = 66
a67 = 67
a68 = 68
a69 = 69
a70 = 70
a71 = 71
a72 = 72
a73 = 73
a74 = 74
a75 = 75
a76 = 76
a77 = 77
a78 = 78
a79 = 79
a80 = 80
a81 = 81
a82 = 82
a83 = 83
a84 = 84
a85 = 85
a86 = 86
a87 = 87
a88 = 88
a89 = 89
a90 = 90
a91 = 91
a92 = 92
a93 = 93
a94 = 94
a95 = 95
a96 = 96
a97 = 97
a98 = 98
a99 = 99
@@ -0,0 +1,14 @@
name = { first = "Tom", last = "Preston-Werner" }
point = {x=1, y=2}
animal = { type.name = "pug" }
contact = {
personal = {
name = "Donald Duck",
email = "donald@duckburg.com",
},
work = {
name = "Coin cleaner",
email = "donald@ScroogeCorp.com",
},
}
@@ -0,0 +1,7 @@
[[arr]]
[arr.subtab]
val=1
[[arr]]
[arr.subtab]
val=2
@@ -0,0 +1,9 @@
[a.b.c] # OK
answer = 42 # OK
[a] # OK - a was non-explicit and was created by std-table-expr
better = 43 # OK - a is now explicit
[t1] # OK
t2.t3.v = 0 # OK
[t1.t2] # should FAIL - t2 was non-explicit but was not created by std-table-expr
@@ -0,0 +1,3 @@
[[tab.arr]]
[tab]
arr.val1=1 # ERROR: arr was previously defined
@@ -0,0 +1,278 @@
#include "../../src/tomlc17.c"
#include <inttypes.h>
const char **g_argv = 0;
int g_argc = 0;
static void usage() {
fprintf(stderr, "Usage: %s [fname]\n", g_argv[0]);
exit(1);
}
// Print a string in json. Properly escape special chars.
static void print_string(const char *s, int len) {
printf("{\"type\": \"string\", \"value\": \"");
for (int i = 0; i < len; i++) {
int ch = s[i];
if (isprint(ch) && ch != '\"' && ch != '\\') {
putchar(ch);
continue;
}
switch (ch) {
case '\b':
putchar('\\');
putchar('b');
break; // Escape backspace
case '\t':
putchar('\\');
putchar('t');
break; // Escape tab
case '\n':
putchar('\\');
putchar('n');
break; // Escape newline
case '\f':
putchar('\\');
putchar('f');
break; // Escape formfeed
case '\r':
putchar('\\');
putchar('r');
break; // Escape carriage return
case '"':
putchar('\\');
putchar('"');
break; // Escape double quotes
case '\\':
putchar('\\');
putchar('\\');
break; // Escape backslash
default:
if (0 <= ch && ch < ' ') {
printf("\\u%04x", ch);
} else {
putchar(ch);
}
break;
}
}
printf("\"}");
}
// Print a key
static void print_key(const char *s, int len) {
putchar('"');
for (int i = 0; i < len; i++) {
int ch = s[i];
if (isprint(ch) && ch != '\"' && ch != '\\') {
putchar(ch);
continue;
}
switch (ch) {
case '"':
putchar('\\');
putchar('"');
break; // Escape double quotes
case '\\':
putchar('\\');
putchar('\\');
break; // Escape backslash
case '\b':
putchar('\\');
putchar('b');
break; // Escape backspace
case '\f':
putchar('\\');
putchar('f');
break; // Escape formfeed
case '\n':
putchar('\\');
putchar('n');
break; // Escape newline
case '\r':
putchar('\\');
putchar('r');
break; // Escape carriage return
case '\t':
putchar('\\');
putchar('t');
break; // Escape tab
default:
if (0 <= ch && ch < ' ') {
printf("\\u%04x", ch);
} else {
putchar(ch);
}
break;
}
}
putchar('"');
}
// Print a DATE datum
static void print_date(toml_datum_t datum) {
printf("{\"type\": \"date-local\", \"value\": \"%04d-%02d-%02d\"}",
datum.u.ts.year, datum.u.ts.month, datum.u.ts.day);
}
// Print a TIME datum
static void print_time(toml_datum_t datum) {
char fracstr[20];
fracstr[0] = fracstr[1] = '\0';
if (datum.u.ts.usec) {
double f = datum.u.ts.usec / 1000000.0;
snprintf(fracstr, sizeof(fracstr), "%.6f", f);
fracstr[5] = '\0'; // millisec precision
}
printf("{\"type\": \"time-local\", \"value\": \"%02d:%02d:%02d%s\"}",
datum.u.ts.hour, datum.u.ts.minute, datum.u.ts.second, fracstr + 1);
}
// Print a DATETIME datum
static void print_datetime(toml_datum_t datum) {
char fracstr[20];
fracstr[0] = fracstr[1] = '\0';
if (datum.u.ts.usec) {
double f = datum.u.ts.usec / 1000000.0;
snprintf(fracstr, sizeof(fracstr), "%.6f", f);
fracstr[5] = '\0'; // millisec precision
}
printf("{\"type\": \"datetime-local\", \"value\": \"%04d-%02d-%02d "
"%02d:%02d:%02d%s\"}",
datum.u.ts.year, datum.u.ts.month, datum.u.ts.day, datum.u.ts.hour,
datum.u.ts.minute, datum.u.ts.second, fracstr + 1);
}
// Print a DATETIMETZ datum
static void print_datetimetz(toml_datum_t datum) {
char fracstr[20];
char tzstr[20];
fracstr[0] = fracstr[1] = tzstr[0] = '\0';
if (datum.u.ts.usec) {
double f = datum.u.ts.usec / 1000000.0;
snprintf(fracstr, sizeof(fracstr), "%.6f", f);
fracstr[5] = '\0'; // millisec precision
}
int tz = datum.u.ts.tz;
char sign = tz < 0 ? '-' : '+';
tz = tz < 0 ? -tz : tz;
snprintf(tzstr, sizeof(tzstr), "%c%02d:%02d", sign, tz / 60, tz % 60);
printf("{\"type\": \"datetime\", \"value\": \"%04d-%02d-%02d "
"%02d:%02d:%02d%s%s\"}",
datum.u.ts.year, datum.u.ts.month, datum.u.ts.day, datum.u.ts.hour,
datum.u.ts.minute, datum.u.ts.second, fracstr + 1, tzstr);
}
// Print indent spaces
int indent_level = 0;
static int indent() {
for (int i = 0; i < indent_level * 2; i++) {
putchar(' ');
}
return 0;
}
static const char *fmt_double(double f, char *buf, int buflen) {
snprintf(buf, buflen, "%.16g", f);
if (!strchr(buf, 'e') && !strchr(buf, '.') && !strchr(buf, 'n')) {
// add a .0 if the number is an int, and not inf or nan.
snprintf(buf, buflen, "%.16g%s", f, ".0");
}
if (isnan(f) && signbit(f)) {
snprintf(buf, buflen, "%s", "-nan");
}
return buf;
}
// Print datum tree recursively
static void print_datum(toml_datum_t datum) {
char buf[50];
switch (datum.type) {
case TOML_STRING:
print_string(datum.u.str.ptr, datum.u.str.len);
break;
case TOML_INT64:
printf("{\"type\": \"integer\", \"value\": \"%" PRId64 "\"}",
datum.u.int64);
break;
case TOML_FP64:
printf("{\"type\": \"float\", \"value\": \"%s\"}",
fmt_double(datum.u.fp64, buf, sizeof(buf)));
break;
case TOML_BOOLEAN:
printf("{\"type\": \"bool\", \"value\": \"%s\"}",
datum.u.boolean ? "true" : "false");
break;
case TOML_DATE:
print_date(datum);
break;
case TOML_TIME:
print_time(datum);
break;
case TOML_DATETIME:
print_datetime(datum);
break;
case TOML_DATETIMETZ:
print_datetimetz(datum);
break;
case TOML_ARRAY:
printf("[");
for (int i = 0; i < datum.u.arr.size; i++) {
printf("%s", i ? ", " : "");
print_datum(datum.u.arr.elem[i]);
}
printf("]");
break;
case TOML_TABLE:
printf("{\n");
indent_level++;
for (int i = 0; i < datum.u.tab.size; i++) {
printf("%s", i ? ",\n" : "");
indent();
print_key(datum.u.tab.key[i], datum.u.tab.len[i]);
putchar(':');
putchar(' ');
print_datum(datum.u.tab.value[i]);
}
printf("\n");
indent_level--;
indent(), printf("}");
break;
default:
fprintf(stderr, "ERROR: unimplemented datum type %d\n", datum.type);
abort();
}
}
int main(int argc, const char *argv[]) {
g_argc = argc;
g_argv = argv;
if (argc > 2) {
usage();
}
toml_option_t opt = toml_default_option();
opt.check_utf8 = 1;
toml_set_option(opt);
toml_result_t result;
if (argc == 2) {
result = toml_parse_file_ex(argv[1]);
} else {
result = toml_parse_file(stdin);
}
if (!result.ok) {
printf("%s\n", result.errmsg);
toml_free(result);
exit(1);
}
print_datum(result.toptab);
printf("\n");
toml_free(result);
return 0;
}
@@ -0,0 +1,20 @@
#!/bin/bash
mkdir -p out
echo
echo =========================
echo == parser test
echo =========================
for fname in {1..200} array{1..10} tab{1..10} x{1..10} e{1..10}; do
IN="in/$fname.toml"
if [ -f $IN ]; then
echo test $fname.toml
OUT="out/$fname.out"
GOOD="good/$fname.out"
./driver $IN &> $OUT || true # ignore failure
diff $GOOD $OUT || { echo '--- FAILED ---'; exit 1; }
fi
done
echo DONE
@@ -0,0 +1,2 @@
/driver
/out
@@ -0,0 +1,21 @@
CFLAGS := -O0 -g -fsanitize=address,undefined -std=c17 -Wmissing-declarations -Wall -Wextra -MMD
all: driver
driver: scankey.c
$(CC) $(CFLAGS) -o $@ scankey.c
test: all
bash run.sh
-include driver.d
clean:
rm -f *.o *.d driver
distclean: clean
format:
clang-format -i *.[ch]
.PHONY: all clean distclean format test
@@ -0,0 +1,5 @@
str1 =
# comment
str2 #comment
@@ -0,0 +1,6 @@
"title" =
'title' =
bare_key =
bare-key =
1234 =
@@ -0,0 +1,4 @@
apple.type =
3.14159 =
1.2 .3 . 4 =
@@ -0,0 +1,7 @@
[table-1]
[dog."tater.man"]
[a.b.c] # this is best practice
[ d.e.f ] # same as [d.e.f]
[ g . h . i ] # same as [g.h.i]
[ j . "ʞ" . 'l' ] # same as [j."ʞ".'l']
@@ -0,0 +1,7 @@
# [x] you
# [x.y] don't
# [x.y.z] need these
[x.y.z.w] # for this to work
[x] # defining a super-table afterward is ok
@@ -0,0 +1,5 @@
name { # error
name } # error
name [ # error
name ] # error
@@ -0,0 +1,3 @@
[[products.1]]
[[products.2]]
@@ -0,0 +1,3 @@
café =
ñoño =
日本語 =
@@ -0,0 +1,20 @@
#!/bin/bash
mkdir -p out
echo
echo =========================
echo == scankey test
echo =========================
for fname in {1..100}; do
IN="in/$fname"
if [ -f $IN ]; then
echo test $fname
OUT="out/$fname.out"
GOOD="good/$fname.out"
./driver $IN &> $OUT
diff $GOOD $OUT || { echo '--- FAILED ---'; exit 1; }
fi
done
echo DONE
@@ -0,0 +1,127 @@
#include "../../src/tomlc17.c"
#include <stdlib.h>
const char **g_argv = 0;
int g_argc = 0;
static void usage() {
fprintf(stderr, "Usage: %s fname\n", g_argv[0]);
exit(1);
}
static void printspecial(const char *p, int n) {
for (int i = 0; i < n; i++, p++) {
int ch = (*p == '\n') ? '_' : *p;
putchar(ch);
}
}
static void printtok(char *content, const token_t tok) {
// clang-format off
#define CASESTR(x) case TOK_ ## x: s = #x; break
// clang-format on
char *s = 0;
switch (tok.toktyp) {
CASESTR(DOT);
CASESTR(EQUAL);
CASESTR(COMMA);
CASESTR(LBRACK);
CASESTR(LLBRACK);
CASESTR(RBRACK);
CASESTR(RRBRACK);
CASESTR(LBRACE);
CASESTR(RBRACE);
CASESTR(STRING);
CASESTR(MLSTRING);
CASESTR(LITSTRING);
CASESTR(MLLITSTRING);
CASESTR(TIME);
CASESTR(DATE);
CASESTR(DATETIME);
CASESTR(DATETIMETZ);
CASESTR(INTEGER);
CASESTR(FLOAT);
CASESTR(BOOL);
CASESTR(LIT);
CASESTR(ENDL);
CASESTR(FIN);
}
printf("%s %ld %d ", s, tok.str.ptr - content, tok.str.len);
printspecial(tok.str.ptr, tok.str.len);
printf("\n");
}
static char *readfile(const char *fname, int *ret_len) {
FILE *fp = fopen(fname, "r");
if (!fp) {
perror("fopen");
exit(1);
}
// Seek to the end of the file to determine its size
if (fseek(fp, 0, SEEK_END) != 0) {
perror("fseek");
exit(1);
}
long file_size = ftell(fp);
if (file_size == -1) {
perror("ftell");
exit(1);
}
rewind(fp); // Go back to the beginning of the file
// Allocate memory for the file content, plus one for the null terminator
char *content = malloc(file_size + 1);
if (!content) {
perror("out of memory");
exit(1);
}
// Read the file into the buffer
size_t read_size = fread(content, 1, file_size, fp);
if (read_size != (size_t)file_size) {
perror("fread");
exit(1);
}
content[file_size] = '\0'; // Null-terminate the string
fclose(fp);
*ret_len = file_size;
return content;
}
int main(int argc, const char *argv[]) {
g_argc = argc;
g_argv = argv;
if (argc != 2) {
usage();
}
int len;
char *content = readfile(argv[1], &len);
char errbuf[200];
scanner_t scanner;
scanner_t *sp = &scanner;
scan_init(sp, content, len, errbuf, sizeof(errbuf));
for (;;) {
(void)scan_value; // silent compiler
token_t tok;
if (scan_key(sp, &tok)) {
break;
}
if (tok.toktyp == TOK_FIN) {
break;
}
printtok(content, tok);
}
if (sp->errmsg) {
printf("ERROR: %s (line %d)\n", sp->errmsg, sp->lineno);
exit(1);
}
free(content);
return 0;
}
@@ -0,0 +1,2 @@
/driver
/out
@@ -0,0 +1,21 @@
CFLAGS := -O0 -g -fsanitize=address,undefined -std=c17 -Wmissing-declarations -Wall -Wextra -MMD
all: driver
driver: scanvalue.c
$(CC) $(CFLAGS) -o $@ scanvalue.c -lm
test: all
bash run.sh
-include driver.d
clean:
rm -f *.o *.d driver
distclean: clean
format:
clang-format -i *.[ch]
.PHONY: all clean distclean format test
@@ -0,0 +1,16 @@
= """
Roses are red
Violets are blue"""
# On a Unix system, the above multi-line string will most likely be the same as:
= "Roses are red\nViolets are blue"
# On a Windows system, it will most likely be equivalent to:
= "Roses are red\r\nViolets are blue"
= """Here are two quotation marks: "". Simple enough."""
= """Here are three quotation marks: ""\"."""
= """Here are fifteen quotation marks: ""\"""\"""\"""\"""\"."""
# "This," she said, "is just a pointless statement."
= """"This," she said, "is just a pointless statement.""""# What you see is what you get.
@@ -0,0 +1,12 @@
# integers
= [ 1, 2, 3 ]
# colors
= [ "red", "yellow", "green" ]
# nested
= [ [ 1, 2 ], [3, 4, 5] ]
= [ [ 1, 2 ], ["a", "b", "c"] ]
= [ "all", 'strings', """are the same""", '''type''' ]
@@ -0,0 +1,6 @@
# Mixed-type arrays are allowed
= [ 0.1, 0.2, 0.5, 1.0, 2, 5 ]
= [
"Foo Bar <foo@example.com>",
{ }
]
@@ -0,0 +1,11 @@
# multiline
= [
1, 2, 3
]
# extra comma at end
= [
1,
2, # this is ok
]
@@ -0,0 +1,2 @@
= { }
@@ -0,0 +1,17 @@
# all results are the same
= "abc"
= """
a\
b\
c"""
= """\
a\
b\
c\
"""
@@ -0,0 +1,9 @@
# line-ending backslash with whitespace "geeee\ "
# result should be "heeee\ngeeee"
= """
heeee
geeee\
"""
@@ -0,0 +1,2 @@
= "\t tab tab tab \t"
= "\e There is no escape! \e"
@@ -0,0 +1 @@
= inf
@@ -0,0 +1,4 @@
= 2023-01-01T12:34:56.123456789Z
= 2023-01-01T12:34:56.123456789
= 07:32:00.123456789

Some files were not shown because too many files have changed in this diff Show More