Archived
add config support
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
# compiler flags
|
# compiler flags
|
||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -Wall -Wextra -Iincludes -Isrc
|
CFLAGS = -Wall -Wextra -Iincludes -Isrc -Iincludes/tomlc17-R260618/src
|
||||||
|
|
||||||
# source & output
|
# source & output
|
||||||
SRC = main.c src/driver.c src/data.c
|
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
|
OBJ = main.o src/driver.o src/data.o src/config.o src/help.o includes/tomlc17-R260618/src/tomlc17.o
|
||||||
TARGET = k1ng_driver
|
TARGET = k1ng_driver
|
||||||
LDFLAGS = -lusb-1.0
|
LDFLAGS = -lusb-1.0
|
||||||
|
|
||||||
@@ -25,6 +25,15 @@ src/driver.o: src/driver.c
|
|||||||
src/data.o: src/data.c
|
src/data.o: src/data.c
|
||||||
$(CC) $(CFLAGS) -c src/data.c -o src/data.o
|
$(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
|
# cleanup build artifacts
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJ) $(TARGET)
|
rm -f $(OBJ) $(TARGET)
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> work in progress, but works!
|
> 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
|
## K1NG-Driver
|
||||||
|
|
||||||
a driver for K1NG PRO (4K) mouse from redragon
|
a driver for K1NG PRO (4K) mouse from redragon
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include "libusb-1.0.30/libusb/libusb.h"
|
#include "libusb-1.0.30/libusb/libusb.h"
|
||||||
|
#include "tomlc17-R260618/src/tomlc17.h"
|
||||||
|
|
||||||
// mouse pids
|
// mouse pids
|
||||||
#define TARGET_VID 0x3554
|
#define TARGET_VID 0x3554
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
*~
|
||||||
|
*.o
|
||||||
|
*.d
|
||||||
|
*.a
|
||||||
|
*.swp
|
||||||
|
a.out
|
||||||
|
t
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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).
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/scanvalue
|
||||||
|
/scankey
|
||||||
|
/parser
|
||||||
|
/*.so.*
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
Reference in New Issue
Block a user