add config support
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
= 'C:\Users\nodejs\templates'
|
||||
= '\\ServerX\admin$\system32\'
|
||||
= 'Tom "Dubs" Preston-Werner'
|
||||
= '<\i\c*\s*>'
|
||||
= '''I [dw]on't need \d{2} apples'''
|
||||
= '''
|
||||
The first newline is
|
||||
trimmed in raw strings.
|
||||
All other whitespace
|
||||
is preserved.
|
||||
'''
|
||||
@@ -0,0 +1,7 @@
|
||||
= '''Here are fifteen quotation marks: """""""""""""""'''
|
||||
|
||||
# apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID
|
||||
= "Here are fifteen apostrophes: '''''''''''''''"
|
||||
|
||||
# 'That,' she said, 'is still pointless.'
|
||||
= ''''That,' she said, 'is still pointless.''''
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
= "pi"
|
||||
= +99
|
||||
= 42
|
||||
= 0
|
||||
= -17
|
||||
= 1_000
|
||||
= 5_349_221
|
||||
= 53_49_221 # Indian number system grouping
|
||||
= 1_2_3_4_5 # VALID but discouraged
|
||||
@@ -0,0 +1,11 @@
|
||||
# hexadecimal with prefix `0x`
|
||||
= 0xDEADBEEF
|
||||
= 0xdeadbeef
|
||||
= 0xdead_beef
|
||||
|
||||
# octal with prefix `0o`
|
||||
= 0o01234567
|
||||
= 0o755 # useful for Unix file permissions
|
||||
|
||||
# binary with prefix `0b`
|
||||
= 0b11010110
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
# fractional
|
||||
= +1.0
|
||||
= 3.1415
|
||||
= -0.01
|
||||
|
||||
# exponent
|
||||
= 5e+22
|
||||
= 1e06
|
||||
= -2E-2
|
||||
|
||||
# both
|
||||
= 6.626e-34
|
||||
|
||||
= 224_617.445_991_228
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
# infinity
|
||||
= inf # positive infinity
|
||||
= +inf # positive infinity
|
||||
= -inf # negative infinity
|
||||
|
||||
# not a number
|
||||
= nan # actual sNaN/qNaN encoding is implementation-specific
|
||||
= +nan # same as `nan`
|
||||
= -nan # valid, actual encoding is implementation-specific
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
= true
|
||||
= false
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= 1979-05-27T07:32:00Z
|
||||
= 1979-05-27T00:32:00-07:00
|
||||
= 1979-05-27T00:32:00.999999-07:00
|
||||
= 1979-05-27 07:32:00Z
|
||||
= 1979-05-27T07:32:00
|
||||
= 1979-05-27T00:32:00.999999
|
||||
= 1979-05-27
|
||||
= 07:32:00
|
||||
= 00:32:00.999999
|
||||
|
||||
# lower
|
||||
= 1987-07-05t17:45:00z
|
||||
@@ -0,0 +1,2 @@
|
||||
# trailing .
|
||||
= 1997-09-09T09:09:09.
|
||||
@@ -0,0 +1,2 @@
|
||||
# timezone offset overflow minute
|
||||
= 1985-06-18 17:04:07+12:60
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
mkdir -p out
|
||||
|
||||
echo
|
||||
echo =========================
|
||||
echo == scanvalue test
|
||||
echo =========================
|
||||
|
||||
for fname in {1..100} e{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,161 @@
|
||||
#include "../../src/tomlc17.c"
|
||||
#include <inttypes.h>
|
||||
#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 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;
|
||||
}
|
||||
|
||||
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(RBRACK);
|
||||
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);
|
||||
default:
|
||||
s = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
printf("%s %ld %d ", s, tok.str.ptr - content, tok.str.len);
|
||||
printspecial(tok.str.ptr, tok.str.len);
|
||||
|
||||
char buf[50];
|
||||
switch (tok.toktyp) {
|
||||
case TOK_INTEGER:
|
||||
printf(" %" PRId64, tok.u.int64);
|
||||
break;
|
||||
case TOK_FLOAT:
|
||||
printf(" %s", fmt_double(tok.u.fp64, buf, sizeof(buf)));
|
||||
break;
|
||||
case TOK_BOOL:
|
||||
printf(" %s", tok.u.b1 ? "true" : "false");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
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_key; // silent compiler
|
||||
token_t tok;
|
||||
if (scan_value(sp, &tok)) {
|
||||
printf("%s\n", errbuf);
|
||||
goto bail;
|
||||
}
|
||||
if (tok.toktyp == TOK_FIN) {
|
||||
break;
|
||||
}
|
||||
printtok(content, tok);
|
||||
}
|
||||
|
||||
if (sp->errmsg) {
|
||||
printf("ERROR: %s (line %d)\n", sp->errmsg, sp->lineno);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
free(content);
|
||||
return 0;
|
||||
|
||||
bail:
|
||||
free(content);
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
test1
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
./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,158 @@
|
||||
#include "../../src/tomlc17.c"
|
||||
|
||||
static void failed(const char *msg) {
|
||||
printf("FAILED: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#define CHECK(x) \
|
||||
if (x) \
|
||||
; \
|
||||
else \
|
||||
failed(#x)
|
||||
|
||||
static void test_named_scalars(void) {
|
||||
printf("Running test_named_scalars...\n");
|
||||
const char *doc = "title = \"hi\"\n"
|
||||
"[owner]\n"
|
||||
"name = \"Alice\"\n"
|
||||
"[[pkg]]\n"
|
||||
"n = 1\n"
|
||||
"nums = [1, 2, 3]\n"
|
||||
"inl = { a = 1, b = 2 }\n";
|
||||
toml_result_t r = toml_parse_named(doc, (int)strlen(doc), "a.toml");
|
||||
CHECK(r.ok);
|
||||
CHECK(r.toptab.source && 0 == strcmp(r.toptab.source, "a.toml"));
|
||||
|
||||
toml_datum_t title = toml_get(r.toptab, "title");
|
||||
CHECK(title.type == TOML_STRING);
|
||||
CHECK(title.source && 0 == strcmp(title.source, "a.toml"));
|
||||
|
||||
toml_datum_t owner = toml_get(r.toptab, "owner");
|
||||
CHECK(owner.type == TOML_TABLE);
|
||||
CHECK(owner.source && 0 == strcmp(owner.source, "a.toml"));
|
||||
toml_datum_t oname = toml_get(owner, "name");
|
||||
CHECK(oname.source && 0 == strcmp(oname.source, "a.toml"));
|
||||
|
||||
toml_datum_t pkg = toml_get(r.toptab, "pkg");
|
||||
CHECK(pkg.type == TOML_ARRAY && pkg.u.arr.size == 1);
|
||||
CHECK(pkg.u.arr.elem[0].source &&
|
||||
0 == strcmp(pkg.u.arr.elem[0].source, "a.toml"));
|
||||
|
||||
// inline array: datum and elements carry source
|
||||
toml_datum_t nums = toml_get(pkg.u.arr.elem[0], "nums");
|
||||
CHECK(nums.type == TOML_ARRAY && nums.u.arr.size == 3);
|
||||
CHECK(nums.source && 0 == strcmp(nums.source, "a.toml"));
|
||||
CHECK(nums.u.arr.elem[1].source &&
|
||||
0 == strcmp(nums.u.arr.elem[1].source, "a.toml"));
|
||||
|
||||
// inline table: datum and values carry source
|
||||
toml_datum_t inl = toml_get(pkg.u.arr.elem[0], "inl");
|
||||
CHECK(inl.type == TOML_TABLE && inl.u.tab.size == 2);
|
||||
CHECK(inl.source && 0 == strcmp(inl.source, "a.toml"));
|
||||
toml_datum_t inl_a = toml_get(inl, "a");
|
||||
CHECK(inl_a.type == TOML_INT64);
|
||||
CHECK(inl_a.source && 0 == strcmp(inl_a.source, "a.toml"));
|
||||
|
||||
// interned: same pointer across datums of one document
|
||||
CHECK(title.source == oname.source);
|
||||
toml_free(r);
|
||||
}
|
||||
|
||||
static void test_null_name(void) {
|
||||
printf("Running test_null_name...\n");
|
||||
const char *doc = "x = 1\n";
|
||||
toml_result_t r1 = toml_parse_named(doc, (int)strlen(doc), NULL);
|
||||
CHECK(r1.ok);
|
||||
CHECK(r1.toptab.source == NULL);
|
||||
CHECK(toml_get(r1.toptab, "x").source == NULL);
|
||||
toml_free(r1);
|
||||
|
||||
toml_result_t r2 = toml_parse(doc, (int)strlen(doc));
|
||||
CHECK(r2.ok);
|
||||
CHECK(toml_get(r2.toptab, "x").source == NULL);
|
||||
toml_free(r2);
|
||||
}
|
||||
|
||||
static void test_file_ex(void) {
|
||||
printf("Running test_file_ex...\n");
|
||||
const char *path = "src_test_tmp.toml";
|
||||
FILE *fp = fopen(path, "w");
|
||||
CHECK(fp != NULL);
|
||||
fputs("k = 42\n", fp);
|
||||
fclose(fp);
|
||||
|
||||
toml_result_t r = toml_parse_file_ex(path);
|
||||
CHECK(r.ok);
|
||||
CHECK(r.toptab.source && 0 == strcmp(r.toptab.source, path));
|
||||
CHECK(toml_get(r.toptab, "k").source &&
|
||||
0 == strcmp(toml_get(r.toptab, "k").source, path));
|
||||
toml_free(r);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
static void test_merge_sources(void) {
|
||||
printf("Running test_merge_sources...\n");
|
||||
const char *a = "title = \"A\"\n"
|
||||
"[owner]\n"
|
||||
"name = \"Alice\"\n";
|
||||
const char *b = "version = \"1\"\n"
|
||||
"[owner]\n"
|
||||
"org = \"ACME\"\n";
|
||||
toml_result_t rA = toml_parse_named(a, (int)strlen(a), "a.toml");
|
||||
toml_result_t rB = toml_parse_named(b, (int)strlen(b), "b.toml");
|
||||
toml_result_t m = toml_merge(&rA, &rB);
|
||||
CHECK(m.ok);
|
||||
|
||||
// free inputs first: merged result must be self-contained
|
||||
toml_free(rA);
|
||||
toml_free(rB);
|
||||
|
||||
CHECK(0 == strcmp(toml_get(m.toptab, "title").source, "a.toml"));
|
||||
CHECK(0 == strcmp(toml_get(m.toptab, "version").source, "b.toml"));
|
||||
|
||||
toml_datum_t owner = toml_get(m.toptab, "owner");
|
||||
toml_datum_t name = toml_get(owner, "name"); // from A
|
||||
toml_datum_t org = toml_get(owner, "org"); // from B
|
||||
CHECK(0 == strcmp(name.source, "a.toml"));
|
||||
CHECK(0 == strcmp(org.source, "b.toml"));
|
||||
|
||||
// dedup: same-origin datums share a pointer
|
||||
CHECK(toml_get(m.toptab, "title").source == name.source);
|
||||
toml_free(m);
|
||||
}
|
||||
|
||||
static void test_merge_array_of_tables_source(void) {
|
||||
printf("Running test_merge_array_of_tables_source...\n");
|
||||
const char *a = "[[pkg]]\n"
|
||||
"name = \"left\"\n";
|
||||
const char *b = "[[pkg]]\n"
|
||||
"name = \"right\"\n";
|
||||
toml_result_t rA = toml_parse_named(a, (int)strlen(a), "a.toml");
|
||||
toml_result_t rB = toml_parse_named(b, (int)strlen(b), "b.toml");
|
||||
toml_result_t m = toml_merge(&rA, &rB);
|
||||
CHECK(m.ok);
|
||||
|
||||
// free inputs first: merged result must be self-contained
|
||||
toml_free(rA);
|
||||
toml_free(rB);
|
||||
|
||||
toml_datum_t pkg = toml_get(m.toptab, "pkg");
|
||||
CHECK(pkg.type == TOML_ARRAY && pkg.u.arr.size == 2);
|
||||
// append semantics: A's element first, B's element appended
|
||||
CHECK(0 == strcmp(pkg.u.arr.elem[0].source, "a.toml"));
|
||||
CHECK(0 == strcmp(pkg.u.arr.elem[1].source, "b.toml"));
|
||||
CHECK(0 == strcmp(toml_get(pkg.u.arr.elem[0], "name").source, "a.toml"));
|
||||
CHECK(0 == strcmp(toml_get(pkg.u.arr.elem[1], "name").source, "b.toml"));
|
||||
toml_free(m);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_named_scalars();
|
||||
test_null_name();
|
||||
test_file_ex();
|
||||
test_merge_sources();
|
||||
test_merge_array_of_tables_source();
|
||||
printf("OK\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/driver
|
||||
@@ -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
|
||||
|
||||
-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,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,10 @@
|
||||
|
||||
echo
|
||||
echo =========================
|
||||
echo == stdtest
|
||||
echo =========================
|
||||
|
||||
#v1.0 ==> go install github.com/toml-lang/toml-test/cmd/toml-test@latest
|
||||
go install github.com/toml-lang/toml-test/v2/cmd/toml-test@latest
|
||||
toml-test test -toml 1.1 -decoder $PWD/driver \
|
||||
-skip invalid/key/special-character # μ (U+03BC) is valid per TOML 1.1 ABNF; toml-test v2 doesn't exclude this 1.0-era test for 1.1
|
||||
Reference in New Issue
Block a user