add config support

This commit is contained in:
2026-07-30 13:18:41 +02:00
parent 215ed9705e
commit dc1aa5e5b6
124 changed files with 7402 additions and 8 deletions
@@ -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