From e8bd3328273ed3df505b9cd7175710319c21b8a8 Mon Sep 17 00:00:00 2001 From: Daniel Ziltener Date: Tue, 6 Sep 2022 13:25:41 +0200 Subject: [PATCH] Squashed 'tomlc99/' content from commit e4107c4 git-subtree-dir: tomlc99 git-subtree-split: e4107c455491925b8982c22df1ce37c0ccb7d4e4 --- .editorconfig | 14 + .gitignore | 38 + LICENSE | 22 + Makefile | 50 + README.md | 194 +++ libtoml.pc.sample | 11 + sample.toml | 3 + stdex/.gitignore | 1 + stdex/RUN.sh | 15 + stdex/arr1.toml | 13 + stdex/arr1.toml.res | 56 + stdex/arr2.toml | 8 + stdex/arr2.toml.res | 11 + stdex/arrtab1.toml | 11 + stdex/arrtab1.toml.res | 15 + stdex/arrtab2.toml | 19 + stdex/arrtab2.toml.res | 27 + stdex/arrtab3.toml | 8 + stdex/arrtab3.toml.res | 1 + stdex/arrtab4.toml | 4 + stdex/arrtab4.toml.res | 1 + stdex/arrtab5.toml | 11 + stdex/arrtab5.toml.res | 1 + stdex/arrtab6.toml | 14 + stdex/arrtab6.toml.res | 1 + stdex/arrtab7.toml | 3 + stdex/arrtab7.toml.res | 19 + stdex/bool1.toml | 3 + stdex/bool1.toml.res | 4 + stdex/comment.toml | 3 + stdex/comment.toml.res | 4 + stdex/float1.toml | 13 + stdex/float1.toml.res | 9 + stdex/float2.toml | 1 + stdex/float2.toml.res | 2 + stdex/float3.toml | 2 + stdex/float3.toml.res | 2 + stdex/float4.toml | 1 + stdex/float4.toml.res | 2 + stdex/float5.toml | 1 + stdex/float5.toml.res | 3 + stdex/float6.toml | 10 + stdex/float6.toml.res | 8 + stdex/inlinetab1.toml | 3 + stdex/inlinetab1.toml.res | 15 + stdex/inlinetab2.toml | 3 + stdex/inlinetab2.toml.res | 1 + stdex/inlinetab3.toml | 3 + stdex/inlinetab3.toml.res | 1 + stdex/int0.toml | 9 + stdex/int0.toml.res | 10 + stdex/int1.toml | 12 + stdex/int1.toml.res | 8 + stdex/keys00.toml | 4 + stdex/keys00.toml.res | 6 + stdex/keys01.toml | 5 + stdex/keys01.toml.res | 7 + stdex/keys02.toml | 1 + stdex/keys02.toml.res | 1 + stdex/keys03.toml | 1 + stdex/keys03.toml.res | 3 + stdex/keys04.toml | 4 + stdex/keys04.toml.res | 10 + stdex/keys05.toml | 3 + stdex/keys05.toml.res | 7 + stdex/keys06.toml | 3 + stdex/keys06.toml.res | 1 + stdex/keys07.toml | 3 + stdex/keys07.toml.res | 1 + stdex/keys08.toml | 5 + stdex/keys08.toml.res | 8 + stdex/keys09.toml | 8 + stdex/keys09.toml.res | 1 + stdex/keys10.toml | 10 + stdex/keys10.toml.res | 12 + stdex/keys11.toml | 9 + stdex/keys11.toml.res | 12 + stdex/keys12.toml | 1 + stdex/keys12.toml.res | 5 + stdex/kvpair0.toml | 1 + stdex/kvpair0.toml.res | 3 + stdex/kvpair1.toml | 1 + stdex/kvpair1.toml.res | 1 + stdex/kvpair2.toml | 1 + stdex/kvpair2.toml.res | 1 + stdex/string0.toml | 1 + stdex/string0.toml.res | 3 + stdex/string1.toml | 9 + stdex/string1.toml.res | 5 + stdex/string3.toml | 15 + stdex/string3.toml.res | 5 + stdex/string4.toml | 7 + stdex/string4.toml.res | 6 + stdex/string5.toml | 5 + stdex/string5.toml.res | 6 + stdex/string6.toml | 7 + stdex/string6.toml.res | 4 + stdex/string7.toml | 4 + stdex/string7.toml.res | 4 + stdex/string8.toml | 3 + stdex/string8.toml.res | 1 + stdex/tab01.toml | 7 + stdex/tab01.toml.res | 10 + stdex/tab02.toml | 2 + stdex/tab02.toml.res | 9 + stdex/tab03.toml | 4 + stdex/tab03.toml.res | 26 + stdex/tab04.toml | 6 + stdex/tab04.toml.res | 10 + stdex/tab05.toml | 7 + stdex/tab05.toml.res | 1 + stdex/tab06.toml | 7 + stdex/tab06.toml.res | 1 + stdex/tab07.toml | 4 + stdex/tab07.toml.res | 10 + stdex/tab08.toml | 8 + stdex/tab08.toml.res | 8 + stdex/tab09.toml | 7 + stdex/tab09.toml.res | 10 + stdex/tab10.toml | 5 + stdex/tab10.toml.res | 1 + stdex/tab11.toml | 9 + stdex/tab11.toml.res | 13 + stdex/ts1.toml | 5 + stdex/ts1.toml.res | 6 + stdex/ts2.toml | 1 + stdex/ts2.toml.res | 3 + stdex/ts3.toml | 2 + stdex/ts3.toml.res | 4 + stdex/ts4.toml | 1 + stdex/ts4.toml.res | 3 + stdex/ts5.toml | 2 + stdex/ts5.toml.res | 4 + test1/.gitignore | 3 + test1/README.md | 22 + test1/build.sh | 9 + test1/extra/array_of_tables.toml | 1 + test1/extra/inline_array.toml | 1 + test1/extra/inline_table.toml | 1 + test1/run.sh | 5 + test2/.gitignore | 1 + test2/Note.txt | 6 + test2/build.sh | 6 + test2/run.sh | 42 + toml.c | 2379 ++++++++++++++++++++++++++++++ toml.h | 175 +++ toml_cat.c | 322 ++++ toml_json.c | 217 +++ toml_sample.c | 60 + unittest/Makefile | 11 + unittest/t1.c | 73 + 151 files changed, 4458 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 libtoml.pc.sample create mode 100644 sample.toml create mode 100644 stdex/.gitignore create mode 100644 stdex/RUN.sh create mode 100644 stdex/arr1.toml create mode 100644 stdex/arr1.toml.res create mode 100644 stdex/arr2.toml create mode 100644 stdex/arr2.toml.res create mode 100644 stdex/arrtab1.toml create mode 100644 stdex/arrtab1.toml.res create mode 100644 stdex/arrtab2.toml create mode 100644 stdex/arrtab2.toml.res create mode 100644 stdex/arrtab3.toml create mode 100644 stdex/arrtab3.toml.res create mode 100644 stdex/arrtab4.toml create mode 100644 stdex/arrtab4.toml.res create mode 100644 stdex/arrtab5.toml create mode 100644 stdex/arrtab5.toml.res create mode 100644 stdex/arrtab6.toml create mode 100644 stdex/arrtab6.toml.res create mode 100644 stdex/arrtab7.toml create mode 100644 stdex/arrtab7.toml.res create mode 100644 stdex/bool1.toml create mode 100644 stdex/bool1.toml.res create mode 100644 stdex/comment.toml create mode 100644 stdex/comment.toml.res create mode 100644 stdex/float1.toml create mode 100644 stdex/float1.toml.res create mode 100644 stdex/float2.toml create mode 100644 stdex/float2.toml.res create mode 100644 stdex/float3.toml create mode 100644 stdex/float3.toml.res create mode 100644 stdex/float4.toml create mode 100644 stdex/float4.toml.res create mode 100644 stdex/float5.toml create mode 100644 stdex/float5.toml.res create mode 100644 stdex/float6.toml create mode 100644 stdex/float6.toml.res create mode 100644 stdex/inlinetab1.toml create mode 100644 stdex/inlinetab1.toml.res create mode 100644 stdex/inlinetab2.toml create mode 100644 stdex/inlinetab2.toml.res create mode 100644 stdex/inlinetab3.toml create mode 100644 stdex/inlinetab3.toml.res create mode 100644 stdex/int0.toml create mode 100644 stdex/int0.toml.res create mode 100644 stdex/int1.toml create mode 100644 stdex/int1.toml.res create mode 100644 stdex/keys00.toml create mode 100644 stdex/keys00.toml.res create mode 100644 stdex/keys01.toml create mode 100644 stdex/keys01.toml.res create mode 100644 stdex/keys02.toml create mode 100644 stdex/keys02.toml.res create mode 100644 stdex/keys03.toml create mode 100644 stdex/keys03.toml.res create mode 100644 stdex/keys04.toml create mode 100644 stdex/keys04.toml.res create mode 100644 stdex/keys05.toml create mode 100644 stdex/keys05.toml.res create mode 100644 stdex/keys06.toml create mode 100644 stdex/keys06.toml.res create mode 100644 stdex/keys07.toml create mode 100644 stdex/keys07.toml.res create mode 100644 stdex/keys08.toml create mode 100644 stdex/keys08.toml.res create mode 100644 stdex/keys09.toml create mode 100644 stdex/keys09.toml.res create mode 100644 stdex/keys10.toml create mode 100644 stdex/keys10.toml.res create mode 100644 stdex/keys11.toml create mode 100644 stdex/keys11.toml.res create mode 100644 stdex/keys12.toml create mode 100644 stdex/keys12.toml.res create mode 100644 stdex/kvpair0.toml create mode 100644 stdex/kvpair0.toml.res create mode 100644 stdex/kvpair1.toml create mode 100644 stdex/kvpair1.toml.res create mode 100644 stdex/kvpair2.toml create mode 100644 stdex/kvpair2.toml.res create mode 100644 stdex/string0.toml create mode 100644 stdex/string0.toml.res create mode 100644 stdex/string1.toml create mode 100644 stdex/string1.toml.res create mode 100644 stdex/string3.toml create mode 100644 stdex/string3.toml.res create mode 100644 stdex/string4.toml create mode 100644 stdex/string4.toml.res create mode 100644 stdex/string5.toml create mode 100644 stdex/string5.toml.res create mode 100644 stdex/string6.toml create mode 100644 stdex/string6.toml.res create mode 100644 stdex/string7.toml create mode 100644 stdex/string7.toml.res create mode 100644 stdex/string8.toml create mode 100644 stdex/string8.toml.res create mode 100644 stdex/tab01.toml create mode 100644 stdex/tab01.toml.res create mode 100644 stdex/tab02.toml create mode 100644 stdex/tab02.toml.res create mode 100644 stdex/tab03.toml create mode 100644 stdex/tab03.toml.res create mode 100644 stdex/tab04.toml create mode 100644 stdex/tab04.toml.res create mode 100644 stdex/tab05.toml create mode 100644 stdex/tab05.toml.res create mode 100644 stdex/tab06.toml create mode 100644 stdex/tab06.toml.res create mode 100644 stdex/tab07.toml create mode 100644 stdex/tab07.toml.res create mode 100644 stdex/tab08.toml create mode 100644 stdex/tab08.toml.res create mode 100644 stdex/tab09.toml create mode 100644 stdex/tab09.toml.res create mode 100644 stdex/tab10.toml create mode 100644 stdex/tab10.toml.res create mode 100644 stdex/tab11.toml create mode 100644 stdex/tab11.toml.res create mode 100644 stdex/ts1.toml create mode 100644 stdex/ts1.toml.res create mode 100644 stdex/ts2.toml create mode 100644 stdex/ts2.toml.res create mode 100644 stdex/ts3.toml create mode 100644 stdex/ts3.toml.res create mode 100644 stdex/ts4.toml create mode 100644 stdex/ts4.toml.res create mode 100644 stdex/ts5.toml create mode 100644 stdex/ts5.toml.res create mode 100644 test1/.gitignore create mode 100644 test1/README.md create mode 100644 test1/build.sh create mode 100644 test1/extra/array_of_tables.toml create mode 100644 test1/extra/inline_array.toml create mode 100644 test1/extra/inline_table.toml create mode 100644 test1/run.sh create mode 100644 test2/.gitignore create mode 100644 test2/Note.txt create mode 100644 test2/build.sh create mode 100644 test2/run.sh create mode 100644 toml.c create mode 100644 toml.h create mode 100644 toml_cat.c create mode 100644 toml_json.c create mode 100644 toml_sample.c create mode 100644 unittest/Makefile create mode 100644 unittest/t1.c diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..804650e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = false +trim_trailing_whitespace = true + +[*.{c,h}] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3017290 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +*~ + +# Object files +*.o +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex +toml_cat +toml_json +toml_sample + +# Debug files +*.dSYM/ +*.su diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bb09e49 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) CK Tan +https://github.com/cktan/tomlc99 + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b525b12 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +prefix ?= /usr/local +HFILES = toml.h +CFILES = toml.c +OBJ = $(CFILES:.c=.o) +EXEC = toml_json toml_cat toml_sample +PCFILE = libtoml.pc + +CFLAGS = -std=c99 -Wall -Wextra -fpic +LIB_VERSION = 1.0 +LIB = libtoml.a +LIB_SHARED = libtoml.so.$(LIB_VERSION) + +# to compile for debug: make DEBUG=1 +# to compile for no debug: make +ifdef DEBUG + CFLAGS += -O0 -g +else + CFLAGS += -O2 -DNDEBUG +endif + + +all: $(LIB) $(LIB_SHARED) $(EXEC) + +*.o: $(HFILES) + +libtoml.a: toml.o + ar -rcs $@ $^ + +libtoml.so.$(LIB_VERSION): toml.o + $(CC) -shared -o $@ $^ + +$(EXEC): $(LIB) + +install: all + install -d ${prefix}/include ${prefix}/lib + install toml.h ${prefix}/include + install $(LIB) ${prefix}/lib + install $(LIB_SHARED) ${prefix}/lib +ifeq "$(prefix)" "/usr/local" + install $(PCFILE) /usr/local/lib/pkgconfig +endif + +clean: + rm -f *.o $(EXEC) $(LIB) $(LIB_SHARED) + + +format: + clang-format -i $(shell find . -name '*.[ch]') + +.PHONY: all clean install format diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c90c26 --- /dev/null +++ b/README.md @@ -0,0 +1,194 @@ +# tomlc99 + +TOML in c99; v1.0 compliant. + +If you are looking for a C++ library, you might try this wrapper: [https://github.com/cktan/tomlcpp](https://github.com/cktan/tomlcpp). + +* Compatible with [TOML v1.0.0](https://toml.io/en/v1.0.0). +* Tested with multiple test suites, including +[BurntSushi/toml-test](https://github.com/BurntSushi/toml-test) and +[iarna/toml-spec-tests](https://github.com/iarna/toml-spec-tests). +* Provides very simple and intuitive interface. + + +## Usage + +Please see the `toml.h` file for details. The following is a simple example that +parses this config file: + +```toml +[server] + host = "www.example.com" + port = [ 8080, 8181, 8282 ] +``` + +These are the usual steps for getting values from a file: + +1. Parse the TOML file. +2. Traverse and locate a table in TOML. +3. Extract values from the table. +4. Free up allocated memory. + +Below is an example of parsing the values from the example table. + +```c +#include +#include +#include +#include +#include "toml.h" + +static void error(const char* msg, const char* msg1) +{ + fprintf(stderr, "ERROR: %s%s\n", msg, msg1?msg1:""); + exit(1); +} + + +int main() +{ + FILE* fp; + char errbuf[200]; + + // 1. Read and parse toml file + fp = fopen("sample.toml", "r"); + if (!fp) { + error("cannot open sample.toml - ", strerror(errno)); + } + + toml_table_t* conf = toml_parse_file(fp, errbuf, sizeof(errbuf)); + fclose(fp); + + if (!conf) { + error("cannot parse - ", errbuf); + } + + // 2. Traverse to a table. + toml_table_t* server = toml_table_in(conf, "server"); + if (!server) { + error("missing [server]", ""); + } + + // 3. Extract values + toml_datum_t host = toml_string_in(server, "host"); + if (!host.ok) { + error("cannot read server.host", ""); + } + + toml_array_t* portarray = toml_array_in(server, "port"); + if (!portarray) { + error("cannot read server.port", ""); + } + + printf("host: %s\n", host.u.s); + printf("port: "); + for (int i = 0; ; i++) { + toml_datum_t port = toml_int_at(portarray, i); + if (!port.ok) break; + printf("%d ", (int)port.u.i); + } + printf("\n"); + + // 4. Free memory + free(host.u.s); + toml_free(conf); + return 0; +} +``` + +#### Accessing Table Content + +TOML tables are dictionaries where lookups are done using string keys. In +general, all access functions on tables are named `toml_*_in(...)`. + +In the normal case, you know the key and its content type, and retrievals can be done +using one of these functions: +```c +toml_string_in(tab, key); +toml_bool_in(tab, key); +toml_int_in(tab, key); +toml_double_in(tab, key); +toml_timestamp_in(tab, key); +toml_table_in(tab, key); +toml_array_in(tab, key); +``` + +You can also interrogate the keys in a table using an integer index: +```c +toml_table_t* tab = toml_parse_file(...); +for (int i = 0; ; i++) { + const char* key = toml_key_in(tab, i); + if (!key) break; + printf("key %d: %s\n", i, key); +} +``` + +#### Accessing Array Content + +TOML arrays can be deref-ed using integer indices. In general, all access methods on arrays are named `toml_*_at()`. + +To obtain the size of an array: +```c +int size = toml_array_nelem(arr); +``` + +To obtain the content of an array, use a valid index and call one of these functions: +```c +toml_string_at(arr, idx); +toml_bool_at(arr, idx); +toml_int_at(arr, idx); +toml_double_at(arr, idx); +toml_timestamp_at(arr, idx); +toml_table_at(arr, idx); +toml_array_at(arr, idx); +``` + +#### toml_datum_t + +Some `toml_*_at` and `toml_*_in` functions return a toml_datum_t +structure. The `ok` flag in the structure indicates if the function +call was successful. If so, you may proceed to read the value +corresponding to the type of the content. + +For example: +``` +toml_datum_t host = toml_string_in(tab, "host"); +if (host.ok) { + printf("host: %s\n", host.u.s); + free(host.u.s); /* FREE applies to string and timestamp types only */ +} +``` + +** IMPORTANT: if the accessed value is a string or a timestamp, you must call `free(datum.u.s)` or `free(datum.u.ts)` respectively after usage. ** + +## Building and installing + +A normal *make* suffices. You can also simply include the +`toml.c` and `toml.h` files in your project. + +Invoking `make install` will install the header and library files into +/usr/local/{include,lib}. + +Alternatively, specify `make install prefix=/a/file/path` to install into +/a/file/path/{include,lib}. + +## Testing + +To test against the standard test set provided by BurntSushi/toml-test: + +```sh +% make +% cd test1 +% bash build.sh # do this once +% bash run.sh # this will run the test suite +``` + + +To test against the standard test set provided by iarna/toml: + +```sh +% make +% cd test2 +% bash build.sh # do this once +% bash run.sh # this will run the test suite +``` diff --git a/libtoml.pc.sample b/libtoml.pc.sample new file mode 100644 index 0000000..e343634 --- /dev/null +++ b/libtoml.pc.sample @@ -0,0 +1,11 @@ +prefix=/usr/local +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: libtoml +URL: https://github.com/cktan/tomlc99/ +Description: TOML C library in c99. +Version: v1.0 +Libs: -L${libdir} -ltoml +Cflags: -I${includedir} diff --git a/sample.toml b/sample.toml new file mode 100644 index 0000000..e613d17 --- /dev/null +++ b/sample.toml @@ -0,0 +1,3 @@ +[server] + host = "example.com" + port = [ 8080, 8181, 8282 ] diff --git a/stdex/.gitignore b/stdex/.gitignore new file mode 100644 index 0000000..e87afd9 --- /dev/null +++ b/stdex/.gitignore @@ -0,0 +1 @@ +/*.out diff --git a/stdex/RUN.sh b/stdex/RUN.sh new file mode 100644 index 0000000..8395baf --- /dev/null +++ b/stdex/RUN.sh @@ -0,0 +1,15 @@ +rm -f *.out +for i in *.toml; do + echo -n $i + ../toml_cat $i >& $i.out + if [ -f $i.res ]; then + if $(diff $i.out $i.res >& /dev/null); then + echo " [OK]" + else + echo " [FAILED]" + fi + else + echo " [?????]" + fi + +done diff --git a/stdex/arr1.toml b/stdex/arr1.toml new file mode 100644 index 0000000..ce497e6 --- /dev/null +++ b/stdex/arr1.toml @@ -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, 2, 5 ] +contributors = [ + "Foo Bar ", + { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } +] + diff --git a/stdex/arr1.toml.res b/stdex/arr1.toml.res new file mode 100644 index 0000000..a3177b0 --- /dev/null +++ b/stdex/arr1.toml.res @@ -0,0 +1,56 @@ +{ + 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", + ], + numbers = [ + 0.100000, + 0.200000, + 0.500000, + 1, + 2, + 5, + ], + contributors = [ + "Foo Bar ", + { + name = "Baz Qux", + email = "bazqux@example.com", + url = "https://example.com/bazqux", + }, + ], +} diff --git a/stdex/arr2.toml b/stdex/arr2.toml new file mode 100644 index 0000000..6acd9ca --- /dev/null +++ b/stdex/arr2.toml @@ -0,0 +1,8 @@ +integers2 = [ + 1, 2, 3 +] + +integers3 = [ + 1, + 2, # this is ok +] diff --git a/stdex/arr2.toml.res b/stdex/arr2.toml.res new file mode 100644 index 0000000..af381cf --- /dev/null +++ b/stdex/arr2.toml.res @@ -0,0 +1,11 @@ +{ + integers2 = [ + 1, + 2, + 3, + ], + integers3 = [ + 1, + 2, + ], +} diff --git a/stdex/arrtab1.toml b/stdex/arrtab1.toml new file mode 100644 index 0000000..544d142 --- /dev/null +++ b/stdex/arrtab1.toml @@ -0,0 +1,11 @@ +[[products]] +name = "Hammer" +sku = 738594937 + +[[products]] # empty table within the array + +[[products]] +name = "Nail" +sku = 284758393 + +color = "gray" diff --git a/stdex/arrtab1.toml.res b/stdex/arrtab1.toml.res new file mode 100644 index 0000000..4e94d8e --- /dev/null +++ b/stdex/arrtab1.toml.res @@ -0,0 +1,15 @@ +{ + products = [ + { + name = "Hammer", + sku = 738594937, + }, + { + }, + { + name = "Nail", + sku = 284758393, + color = "gray", + }, + ], +} diff --git a/stdex/arrtab2.toml b/stdex/arrtab2.toml new file mode 100644 index 0000000..7a3971f --- /dev/null +++ b/stdex/arrtab2.toml @@ -0,0 +1,19 @@ +[[fruits]] +name = "apple" + +[fruits.physical] # subtable +color = "red" +shape = "round" + +[[fruits.varieties]] # nested array of tables +name = "red delicious" + +[[fruits.varieties]] +name = "granny smith" + + +[[fruits]] +name = "banana" + +[[fruits.varieties]] +name = "plantain" diff --git a/stdex/arrtab2.toml.res b/stdex/arrtab2.toml.res new file mode 100644 index 0000000..d889196 --- /dev/null +++ b/stdex/arrtab2.toml.res @@ -0,0 +1,27 @@ +{ + fruits = [ + { + name = "apple", + varieties = [ + { + name = "red delicious", + }, + { + name = "granny smith", + }, + ], + physical = { + color = "red", + shape = "round", + }, + }, + { + name = "banana", + varieties = [ + { + name = "plantain", + }, + ], + }, + ], +} diff --git a/stdex/arrtab3.toml b/stdex/arrtab3.toml new file mode 100644 index 0000000..703130e --- /dev/null +++ b/stdex/arrtab3.toml @@ -0,0 +1,8 @@ +# INVALID TOML DOC +[fruit.physical] # subtable, but to which parent element should it belong? +color = "red" +shape = "round" + +[[fruit]] # parser must throw an error upon discovering that "fruit" is + # an array rather than a table +name = "apple" diff --git a/stdex/arrtab3.toml.res b/stdex/arrtab3.toml.res new file mode 100644 index 0000000..b57e247 --- /dev/null +++ b/stdex/arrtab3.toml.res @@ -0,0 +1 @@ +ERROR: line 6: key exists diff --git a/stdex/arrtab4.toml b/stdex/arrtab4.toml new file mode 100644 index 0000000..65b2505 --- /dev/null +++ b/stdex/arrtab4.toml @@ -0,0 +1,4 @@ +# INVALID TOML DOC +fruits = [] + +[[fruits]] # Not allowed diff --git a/stdex/arrtab4.toml.res b/stdex/arrtab4.toml.res new file mode 100644 index 0000000..4a89d92 --- /dev/null +++ b/stdex/arrtab4.toml.res @@ -0,0 +1 @@ +ERROR: line 4: array mismatch diff --git a/stdex/arrtab5.toml b/stdex/arrtab5.toml new file mode 100644 index 0000000..b36540e --- /dev/null +++ b/stdex/arrtab5.toml @@ -0,0 +1,11 @@ +# INVALID TOML DOC +[[fruits]] +name = "apple" + +[[fruits.varieties]] +name = "red delicious" + +# INVALID: This table conflicts with the previous array of tables +[fruits.varieties] +name = "granny smith" + diff --git a/stdex/arrtab5.toml.res b/stdex/arrtab5.toml.res new file mode 100644 index 0000000..617cdf1 --- /dev/null +++ b/stdex/arrtab5.toml.res @@ -0,0 +1 @@ +ERROR: line 9: key exists diff --git a/stdex/arrtab6.toml b/stdex/arrtab6.toml new file mode 100644 index 0000000..8324c88 --- /dev/null +++ b/stdex/arrtab6.toml @@ -0,0 +1,14 @@ +# INVALID TOML DOC +[[fruits]] +name = "apple" + +[[fruits.varieties]] +name = "red delicious" + +[fruits.physical] +color = "red" +shape = "round" + +# INVALID: This array of tables conflicts with the previous table +[[fruits.physical]] +color = "green" diff --git a/stdex/arrtab6.toml.res b/stdex/arrtab6.toml.res new file mode 100644 index 0000000..b9a224d --- /dev/null +++ b/stdex/arrtab6.toml.res @@ -0,0 +1 @@ +ERROR: line 13: key exists diff --git a/stdex/arrtab7.toml b/stdex/arrtab7.toml new file mode 100644 index 0000000..ba5584a --- /dev/null +++ b/stdex/arrtab7.toml @@ -0,0 +1,3 @@ +points = [ { x = 1, y = 2, z = 3 }, + { x = 7, y = 8, z = 9 }, + { x = 2, y = 4, z = 8 } ] diff --git a/stdex/arrtab7.toml.res b/stdex/arrtab7.toml.res new file mode 100644 index 0000000..aa0ef41 --- /dev/null +++ b/stdex/arrtab7.toml.res @@ -0,0 +1,19 @@ +{ + points = [ + { + x = 1, + y = 2, + z = 3, + }, + { + x = 7, + y = 8, + z = 9, + }, + { + x = 2, + y = 4, + z = 8, + }, + ], +} diff --git a/stdex/bool1.toml b/stdex/bool1.toml new file mode 100644 index 0000000..6a53ecc --- /dev/null +++ b/stdex/bool1.toml @@ -0,0 +1,3 @@ +bool1 = true +bool2 = false + diff --git a/stdex/bool1.toml.res b/stdex/bool1.toml.res new file mode 100644 index 0000000..76a0816 --- /dev/null +++ b/stdex/bool1.toml.res @@ -0,0 +1,4 @@ +{ + bool1 = true, + bool2 = false, +} diff --git a/stdex/comment.toml b/stdex/comment.toml new file mode 100644 index 0000000..fae57c6 --- /dev/null +++ b/stdex/comment.toml @@ -0,0 +1,3 @@ +# This is a full-line comment +key = "value" # This is a comment at the end of a line +another = "# This is not a comment" diff --git a/stdex/comment.toml.res b/stdex/comment.toml.res new file mode 100644 index 0000000..ae1a16e --- /dev/null +++ b/stdex/comment.toml.res @@ -0,0 +1,4 @@ +{ + key = "value", + another = "# This is not a comment", +} diff --git a/stdex/float1.toml b/stdex/float1.toml new file mode 100644 index 0000000..faa5c4f --- /dev/null +++ b/stdex/float1.toml @@ -0,0 +1,13 @@ +# fractional +flt1 = +1.0 +flt2 = 3.1415 +flt3 = -0.01 + +# exponent +flt4 = 5e+22 +flt5 = 1e06 +flt6 = -2E-2 + +# both +flt7 = 6.626e-34 + diff --git a/stdex/float1.toml.res b/stdex/float1.toml.res new file mode 100644 index 0000000..a01bf36 --- /dev/null +++ b/stdex/float1.toml.res @@ -0,0 +1,9 @@ +{ + flt1 = 1.000000, + flt2 = 3.141500, + flt3 = -0.010000, + flt4 = 49999999999999995805696.000000, + flt5 = 1000000.000000, + flt6 = -0.020000, + flt7 = 0.000000, +} diff --git a/stdex/float2.toml b/stdex/float2.toml new file mode 100644 index 0000000..7744e4f --- /dev/null +++ b/stdex/float2.toml @@ -0,0 +1 @@ +invalid_float_1 = .7 diff --git a/stdex/float2.toml.res b/stdex/float2.toml.res new file mode 100644 index 0000000..9af2622 --- /dev/null +++ b/stdex/float2.toml.res @@ -0,0 +1,2 @@ +{ +ERROR: unable to decode value in table diff --git a/stdex/float3.toml b/stdex/float3.toml new file mode 100644 index 0000000..508260e --- /dev/null +++ b/stdex/float3.toml @@ -0,0 +1,2 @@ +invalid_float_2 = 7. + diff --git a/stdex/float3.toml.res b/stdex/float3.toml.res new file mode 100644 index 0000000..9af2622 --- /dev/null +++ b/stdex/float3.toml.res @@ -0,0 +1,2 @@ +{ +ERROR: unable to decode value in table diff --git a/stdex/float4.toml b/stdex/float4.toml new file mode 100644 index 0000000..c50ef3b --- /dev/null +++ b/stdex/float4.toml @@ -0,0 +1 @@ +invalid_float_3 = 3.e+20 diff --git a/stdex/float4.toml.res b/stdex/float4.toml.res new file mode 100644 index 0000000..9af2622 --- /dev/null +++ b/stdex/float4.toml.res @@ -0,0 +1,2 @@ +{ +ERROR: unable to decode value in table diff --git a/stdex/float5.toml b/stdex/float5.toml new file mode 100644 index 0000000..3c8f0aa --- /dev/null +++ b/stdex/float5.toml @@ -0,0 +1 @@ +flt8 = 224_617.445_991_228 diff --git a/stdex/float5.toml.res b/stdex/float5.toml.res new file mode 100644 index 0000000..1ffb208 --- /dev/null +++ b/stdex/float5.toml.res @@ -0,0 +1,3 @@ +{ + flt8 = 224617.445991, +} diff --git a/stdex/float6.toml b/stdex/float6.toml new file mode 100644 index 0000000..2e77cef --- /dev/null +++ b/stdex/float6.toml @@ -0,0 +1,10 @@ +# 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 + diff --git a/stdex/float6.toml.res b/stdex/float6.toml.res new file mode 100644 index 0000000..95f0a7f --- /dev/null +++ b/stdex/float6.toml.res @@ -0,0 +1,8 @@ +{ + sf1 = inf, + sf2 = inf, + sf3 = -inf, + sf4 = nan, + sf5 = nan, + sf6 = nan, +} diff --git a/stdex/inlinetab1.toml b/stdex/inlinetab1.toml new file mode 100644 index 0000000..26062b5 --- /dev/null +++ b/stdex/inlinetab1.toml @@ -0,0 +1,3 @@ +name = { first = "Tom", last = "Preston-Werner" } +point = { x = 1, y = 2 } +animal = { type.name = "pug" } diff --git a/stdex/inlinetab1.toml.res b/stdex/inlinetab1.toml.res new file mode 100644 index 0000000..7553b02 --- /dev/null +++ b/stdex/inlinetab1.toml.res @@ -0,0 +1,15 @@ +{ + name = { + first = "Tom", + last = "Preston-Werner", + }, + point = { + x = 1, + y = 2, + }, + animal = { + type = { + name = "pug", + }, + }, +} diff --git a/stdex/inlinetab2.toml b/stdex/inlinetab2.toml new file mode 100644 index 0000000..7689e14 --- /dev/null +++ b/stdex/inlinetab2.toml @@ -0,0 +1,3 @@ +[product] +type = { name = "Nail" } +type.edible = false # INVALID diff --git a/stdex/inlinetab2.toml.res b/stdex/inlinetab2.toml.res new file mode 100644 index 0000000..9684a3d --- /dev/null +++ b/stdex/inlinetab2.toml.res @@ -0,0 +1 @@ +ERROR: line 3: cannot insert new entry into existing table diff --git a/stdex/inlinetab3.toml b/stdex/inlinetab3.toml new file mode 100644 index 0000000..d6937eb --- /dev/null +++ b/stdex/inlinetab3.toml @@ -0,0 +1,3 @@ +[product] +type.name = "Nail" +type = { edible = false } # INVALID diff --git a/stdex/inlinetab3.toml.res b/stdex/inlinetab3.toml.res new file mode 100644 index 0000000..0a6a07c --- /dev/null +++ b/stdex/inlinetab3.toml.res @@ -0,0 +1 @@ +ERROR: line 3: key exists diff --git a/stdex/int0.toml b/stdex/int0.toml new file mode 100644 index 0000000..107a6b7 --- /dev/null +++ b/stdex/int0.toml @@ -0,0 +1,9 @@ +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 + diff --git a/stdex/int0.toml.res b/stdex/int0.toml.res new file mode 100644 index 0000000..4e350ca --- /dev/null +++ b/stdex/int0.toml.res @@ -0,0 +1,10 @@ +{ + int1 = 99, + int2 = 42, + int3 = 0, + int4 = -17, + int5 = 1000, + int6 = 5349221, + int7 = 5349221, + int8 = 12345, +} diff --git a/stdex/int1.toml b/stdex/int1.toml new file mode 100644 index 0000000..03bf25b --- /dev/null +++ b/stdex/int1.toml @@ -0,0 +1,12 @@ +# 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 + diff --git a/stdex/int1.toml.res b/stdex/int1.toml.res new file mode 100644 index 0000000..3e8e1bd --- /dev/null +++ b/stdex/int1.toml.res @@ -0,0 +1,8 @@ +{ + hex1 = 3735928559, + hex2 = 3735928559, + hex3 = 3735928559, + oct1 = 342391, + oct2 = 493, + bin1 = 214, +} diff --git a/stdex/keys00.toml b/stdex/keys00.toml new file mode 100644 index 0000000..657ff24 --- /dev/null +++ b/stdex/keys00.toml @@ -0,0 +1,4 @@ +key = "value" +bare_key = "value" +bare-key = "value" +1234 = "value" diff --git a/stdex/keys00.toml.res b/stdex/keys00.toml.res new file mode 100644 index 0000000..b28b39e --- /dev/null +++ b/stdex/keys00.toml.res @@ -0,0 +1,6 @@ +{ + key = "value", + bare_key = "value", + bare-key = "value", + 1234 = "value", +} diff --git a/stdex/keys01.toml b/stdex/keys01.toml new file mode 100644 index 0000000..cc2d1d0 --- /dev/null +++ b/stdex/keys01.toml @@ -0,0 +1,5 @@ +"127.0.0.1" = "value" +"character encoding" = "value" +"ʎǝʞ" = "value" +'key2' = "value" +'quoted "value"' = "value" diff --git a/stdex/keys01.toml.res b/stdex/keys01.toml.res new file mode 100644 index 0000000..5fc4091 --- /dev/null +++ b/stdex/keys01.toml.res @@ -0,0 +1,7 @@ +{ + 127.0.0.1 = "value", + character encoding = "value", + ʎǝʞ = "value", + key2 = "value", + quoted "value" = "value", +} diff --git a/stdex/keys02.toml b/stdex/keys02.toml new file mode 100644 index 0000000..cd9fa90 --- /dev/null +++ b/stdex/keys02.toml @@ -0,0 +1 @@ += "no key name" # INVALID diff --git a/stdex/keys02.toml.res b/stdex/keys02.toml.res new file mode 100644 index 0000000..13a97f7 --- /dev/null +++ b/stdex/keys02.toml.res @@ -0,0 +1 @@ +ERROR: line 1: syntax error diff --git a/stdex/keys03.toml b/stdex/keys03.toml new file mode 100644 index 0000000..5aea8ee --- /dev/null +++ b/stdex/keys03.toml @@ -0,0 +1 @@ +"" = "blank" # VALID but discouraged diff --git a/stdex/keys03.toml.res b/stdex/keys03.toml.res new file mode 100644 index 0000000..1478e36 --- /dev/null +++ b/stdex/keys03.toml.res @@ -0,0 +1,3 @@ +{ + = "blank", +} diff --git a/stdex/keys04.toml b/stdex/keys04.toml new file mode 100644 index 0000000..8f83571 --- /dev/null +++ b/stdex/keys04.toml @@ -0,0 +1,4 @@ +name = "Orange" +physical.color = "orange" +physical.shape = "round" +site."google.com" = true diff --git a/stdex/keys04.toml.res b/stdex/keys04.toml.res new file mode 100644 index 0000000..8bedb21 --- /dev/null +++ b/stdex/keys04.toml.res @@ -0,0 +1,10 @@ +{ + name = "Orange", + physical = { + color = "orange", + shape = "round", + }, + site = { + google.com = true, + }, +} diff --git a/stdex/keys05.toml b/stdex/keys05.toml new file mode 100644 index 0000000..75bf90b --- /dev/null +++ b/stdex/keys05.toml @@ -0,0 +1,3 @@ +fruit.name = "banana" # this is best practice +fruit. color = "yellow" # same as fruit.color +fruit . flavor = "banana" # same as fruit.flavor diff --git a/stdex/keys05.toml.res b/stdex/keys05.toml.res new file mode 100644 index 0000000..33db031 --- /dev/null +++ b/stdex/keys05.toml.res @@ -0,0 +1,7 @@ +{ + fruit = { + name = "banana", + color = "yellow", + flavor = "banana", + }, +} diff --git a/stdex/keys06.toml b/stdex/keys06.toml new file mode 100644 index 0000000..7847bd4 --- /dev/null +++ b/stdex/keys06.toml @@ -0,0 +1,3 @@ +# DO NOT DO THIS +name = "Tom" +name = "Pradyun" diff --git a/stdex/keys06.toml.res b/stdex/keys06.toml.res new file mode 100644 index 0000000..0a6a07c --- /dev/null +++ b/stdex/keys06.toml.res @@ -0,0 +1 @@ +ERROR: line 3: key exists diff --git a/stdex/keys07.toml b/stdex/keys07.toml new file mode 100644 index 0000000..acff1cc --- /dev/null +++ b/stdex/keys07.toml @@ -0,0 +1,3 @@ +# THIS WILL NOT WORK +spelling = "favorite" +"spelling" = "favourite" diff --git a/stdex/keys07.toml.res b/stdex/keys07.toml.res new file mode 100644 index 0000000..0a6a07c --- /dev/null +++ b/stdex/keys07.toml.res @@ -0,0 +1 @@ +ERROR: line 3: key exists diff --git a/stdex/keys08.toml b/stdex/keys08.toml new file mode 100644 index 0000000..b4d1b37 --- /dev/null +++ b/stdex/keys08.toml @@ -0,0 +1,5 @@ +# This makes the key "fruit" into a table. +fruit.apple.smooth = true + +# So then you can add to the table "fruit" like so: +fruit.orange = 2 diff --git a/stdex/keys08.toml.res b/stdex/keys08.toml.res new file mode 100644 index 0000000..df743ec --- /dev/null +++ b/stdex/keys08.toml.res @@ -0,0 +1,8 @@ +{ + fruit = { + orange = 2, + apple = { + smooth = true, + }, + }, +} diff --git a/stdex/keys09.toml b/stdex/keys09.toml new file mode 100644 index 0000000..a6c5ea6 --- /dev/null +++ b/stdex/keys09.toml @@ -0,0 +1,8 @@ +# THE FOLLOWING IS INVALID + +# This defines the value of fruit.apple to be an integer. +fruit.apple = 1 + +# But then this treats fruit.apple like it's a table. +# You can't turn an integer into a table. +fruit.apple.smooth = true diff --git a/stdex/keys09.toml.res b/stdex/keys09.toml.res new file mode 100644 index 0000000..9365cbc --- /dev/null +++ b/stdex/keys09.toml.res @@ -0,0 +1 @@ +ERROR: line 8: key exists diff --git a/stdex/keys10.toml b/stdex/keys10.toml new file mode 100644 index 0000000..dc94a8c --- /dev/null +++ b/stdex/keys10.toml @@ -0,0 +1,10 @@ +# VALID BUT DISCOURAGED + +apple.type = "fruit" +orange.type = "fruit" + +apple.skin = "thin" +orange.skin = "thick" + +apple.color = "red" +orange.color = "orange" diff --git a/stdex/keys10.toml.res b/stdex/keys10.toml.res new file mode 100644 index 0000000..fac0f25 --- /dev/null +++ b/stdex/keys10.toml.res @@ -0,0 +1,12 @@ +{ + apple = { + type = "fruit", + skin = "thin", + color = "red", + }, + orange = { + type = "fruit", + skin = "thick", + color = "orange", + }, +} diff --git a/stdex/keys11.toml b/stdex/keys11.toml new file mode 100644 index 0000000..705686c --- /dev/null +++ b/stdex/keys11.toml @@ -0,0 +1,9 @@ +# RECOMMENDED + +apple.type = "fruit" +apple.skin = "thin" +apple.color = "red" + +orange.type = "fruit" +orange.skin = "thick" +orange.color = "orange" diff --git a/stdex/keys11.toml.res b/stdex/keys11.toml.res new file mode 100644 index 0000000..fac0f25 --- /dev/null +++ b/stdex/keys11.toml.res @@ -0,0 +1,12 @@ +{ + apple = { + type = "fruit", + skin = "thin", + color = "red", + }, + orange = { + type = "fruit", + skin = "thick", + color = "orange", + }, +} diff --git a/stdex/keys12.toml b/stdex/keys12.toml new file mode 100644 index 0000000..2efd659 --- /dev/null +++ b/stdex/keys12.toml @@ -0,0 +1 @@ +3.14159 = "pi" diff --git a/stdex/keys12.toml.res b/stdex/keys12.toml.res new file mode 100644 index 0000000..3f59e3a --- /dev/null +++ b/stdex/keys12.toml.res @@ -0,0 +1,5 @@ +{ + 3 = { + 14159 = "pi", + }, +} diff --git a/stdex/kvpair0.toml b/stdex/kvpair0.toml new file mode 100644 index 0000000..e5b34eb --- /dev/null +++ b/stdex/kvpair0.toml @@ -0,0 +1 @@ +key = "value" diff --git a/stdex/kvpair0.toml.res b/stdex/kvpair0.toml.res new file mode 100644 index 0000000..4b640b6 --- /dev/null +++ b/stdex/kvpair0.toml.res @@ -0,0 +1,3 @@ +{ + key = "value", +} diff --git a/stdex/kvpair1.toml b/stdex/kvpair1.toml new file mode 100644 index 0000000..56f085a --- /dev/null +++ b/stdex/kvpair1.toml @@ -0,0 +1 @@ +key = # INVALID diff --git a/stdex/kvpair1.toml.res b/stdex/kvpair1.toml.res new file mode 100644 index 0000000..13a97f7 --- /dev/null +++ b/stdex/kvpair1.toml.res @@ -0,0 +1 @@ +ERROR: line 1: syntax error diff --git a/stdex/kvpair2.toml b/stdex/kvpair2.toml new file mode 100644 index 0000000..e05c47c --- /dev/null +++ b/stdex/kvpair2.toml @@ -0,0 +1 @@ +first = "Tom" last = "Preston-Werner" # INVALID diff --git a/stdex/kvpair2.toml.res b/stdex/kvpair2.toml.res new file mode 100644 index 0000000..a307757 --- /dev/null +++ b/stdex/kvpair2.toml.res @@ -0,0 +1 @@ +ERROR: line 1: extra chars after value diff --git a/stdex/string0.toml b/stdex/string0.toml new file mode 100644 index 0000000..b611549 --- /dev/null +++ b/stdex/string0.toml @@ -0,0 +1 @@ +str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." diff --git a/stdex/string0.toml.res b/stdex/string0.toml.res new file mode 100644 index 0000000..8b6e2e5 --- /dev/null +++ b/stdex/string0.toml.res @@ -0,0 +1,3 @@ +{ + str = "I'm a string. \"You can quote me\". Name\tJos\0xc3\0xa9\nLocation\tSF.", +} diff --git a/stdex/string1.toml b/stdex/string1.toml new file mode 100644 index 0000000..74c2f51 --- /dev/null +++ b/stdex/string1.toml @@ -0,0 +1,9 @@ +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" diff --git a/stdex/string1.toml.res b/stdex/string1.toml.res new file mode 100644 index 0000000..364437d --- /dev/null +++ b/stdex/string1.toml.res @@ -0,0 +1,5 @@ +{ + str1 = "Roses are red\nViolets are blue", + str2 = "Roses are red\nViolets are blue", + str3 = "Roses are red\r\nViolets are blue", +} diff --git a/stdex/string3.toml b/stdex/string3.toml new file mode 100644 index 0000000..20b4aa7 --- /dev/null +++ b/stdex/string3.toml @@ -0,0 +1,15 @@ +# 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.\ + """ diff --git a/stdex/string3.toml.res b/stdex/string3.toml.res new file mode 100644 index 0000000..f2f6886 --- /dev/null +++ b/stdex/string3.toml.res @@ -0,0 +1,5 @@ +{ + 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.", +} diff --git a/stdex/string4.toml b/stdex/string4.toml new file mode 100644 index 0000000..8f51b11 --- /dev/null +++ b/stdex/string4.toml @@ -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."""" diff --git a/stdex/string4.toml.res b/stdex/string4.toml.res new file mode 100644 index 0000000..e6289b2 --- /dev/null +++ b/stdex/string4.toml.res @@ -0,0 +1,6 @@ +{ + str4 = "Here are two quotation marks: \"\". Simple enough.", + str5 = "Here are three quotation marks: \"\"\".", + str6 = "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\".", + str7 = "\"This,\" she said, \"is just a pointless statement.\"", +} diff --git a/stdex/string5.toml b/stdex/string5.toml new file mode 100644 index 0000000..36772bb --- /dev/null +++ b/stdex/string5.toml @@ -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*>' diff --git a/stdex/string5.toml.res b/stdex/string5.toml.res new file mode 100644 index 0000000..31a4447 --- /dev/null +++ b/stdex/string5.toml.res @@ -0,0 +1,6 @@ +{ + winpath = "C:\\Users\\nodejs\\templates", + winpath2 = "\\\\ServerX\\admin$\\system32\\", + quoted = "Tom \"Dubs\" Preston-Werner", + regex = "<\\i\\c*\\s*>", +} diff --git a/stdex/string6.toml b/stdex/string6.toml new file mode 100644 index 0000000..bc88494 --- /dev/null +++ b/stdex/string6.toml @@ -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. +''' diff --git a/stdex/string6.toml.res b/stdex/string6.toml.res new file mode 100644 index 0000000..7ed3a14 --- /dev/null +++ b/stdex/string6.toml.res @@ -0,0 +1,4 @@ +{ + regex2 = "I [dw]on't need \\d{2} apples", + lines = "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n", +} diff --git a/stdex/string7.toml b/stdex/string7.toml new file mode 100644 index 0000000..f87153e --- /dev/null +++ b/stdex/string7.toml @@ -0,0 +1,4 @@ +quot15 = '''Here are fifteen quotation marks: """""""""""""""''' + +# 'That,' she said, 'is still pointless.' +str = ''''That,' she said, 'is still pointless.'''' diff --git a/stdex/string7.toml.res b/stdex/string7.toml.res new file mode 100644 index 0000000..f194093 --- /dev/null +++ b/stdex/string7.toml.res @@ -0,0 +1,4 @@ +{ + quot15 = "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"", + str = "'That,' she said, 'is still pointless.'", +} diff --git a/stdex/string8.toml b/stdex/string8.toml new file mode 100644 index 0000000..fd74bf2 --- /dev/null +++ b/stdex/string8.toml @@ -0,0 +1,3 @@ +# apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID +apos15 = "Here are fifteen apostrophes: '''''''''''''''" + diff --git a/stdex/string8.toml.res b/stdex/string8.toml.res new file mode 100644 index 0000000..cb2526f --- /dev/null +++ b/stdex/string8.toml.res @@ -0,0 +1 @@ +ERROR: line 2: triple-s-quote inside string lit diff --git a/stdex/tab01.toml b/stdex/tab01.toml new file mode 100644 index 0000000..6b4884c --- /dev/null +++ b/stdex/tab01.toml @@ -0,0 +1,7 @@ +[table-1] +key1 = "some string" +key2 = 123 + +[table-2] +key1 = "another string" +key2 = 456 diff --git a/stdex/tab01.toml.res b/stdex/tab01.toml.res new file mode 100644 index 0000000..0418849 --- /dev/null +++ b/stdex/tab01.toml.res @@ -0,0 +1,10 @@ +{ + table-1 = { + key1 = "some string", + key2 = 123, + }, + table-2 = { + key1 = "another string", + key2 = 456, + }, +} diff --git a/stdex/tab02.toml b/stdex/tab02.toml new file mode 100644 index 0000000..32cbe79 --- /dev/null +++ b/stdex/tab02.toml @@ -0,0 +1,2 @@ +[dog."tater.man"] +type.name = "pug" diff --git a/stdex/tab02.toml.res b/stdex/tab02.toml.res new file mode 100644 index 0000000..82cfae1 --- /dev/null +++ b/stdex/tab02.toml.res @@ -0,0 +1,9 @@ +{ + dog = { + tater.man = { + type = { + name = "pug", + }, + }, + }, +} diff --git a/stdex/tab03.toml b/stdex/tab03.toml new file mode 100644 index 0000000..0b10db3 --- /dev/null +++ b/stdex/tab03.toml @@ -0,0 +1,4 @@ +[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'] diff --git a/stdex/tab03.toml.res b/stdex/tab03.toml.res new file mode 100644 index 0000000..983d6cb --- /dev/null +++ b/stdex/tab03.toml.res @@ -0,0 +1,26 @@ +{ + a = { + b = { + c = { + }, + }, + }, + d = { + e = { + f = { + }, + }, + }, + g = { + h = { + i = { + }, + }, + }, + j = { + ʞ = { + l = { + }, + }, + }, +} diff --git a/stdex/tab04.toml b/stdex/tab04.toml new file mode 100644 index 0000000..256e6c0 --- /dev/null +++ b/stdex/tab04.toml @@ -0,0 +1,6 @@ +# [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 diff --git a/stdex/tab04.toml.res b/stdex/tab04.toml.res new file mode 100644 index 0000000..718d94a --- /dev/null +++ b/stdex/tab04.toml.res @@ -0,0 +1,10 @@ +{ + x = { + y = { + z = { + w = { + }, + }, + }, + }, +} diff --git a/stdex/tab05.toml b/stdex/tab05.toml new file mode 100644 index 0000000..0704ba7 --- /dev/null +++ b/stdex/tab05.toml @@ -0,0 +1,7 @@ +# DO NOT DO THIS + +[fruit] +apple = "red" + +[fruit] +orange = "orange" diff --git a/stdex/tab05.toml.res b/stdex/tab05.toml.res new file mode 100644 index 0000000..b57e247 --- /dev/null +++ b/stdex/tab05.toml.res @@ -0,0 +1 @@ +ERROR: line 6: key exists diff --git a/stdex/tab06.toml b/stdex/tab06.toml new file mode 100644 index 0000000..e58bf8e --- /dev/null +++ b/stdex/tab06.toml @@ -0,0 +1,7 @@ +# DO NOT DO THIS EITHER + +[fruit] +apple = "red" + +[fruit.apple] +texture = "smooth" diff --git a/stdex/tab06.toml.res b/stdex/tab06.toml.res new file mode 100644 index 0000000..b57e247 --- /dev/null +++ b/stdex/tab06.toml.res @@ -0,0 +1 @@ +ERROR: line 6: key exists diff --git a/stdex/tab07.toml b/stdex/tab07.toml new file mode 100644 index 0000000..03fe3a3 --- /dev/null +++ b/stdex/tab07.toml @@ -0,0 +1,4 @@ +# VALID BUT DISCOURAGED +[fruit.apple] +[animal] +[fruit.orange] diff --git a/stdex/tab07.toml.res b/stdex/tab07.toml.res new file mode 100644 index 0000000..239ced9 --- /dev/null +++ b/stdex/tab07.toml.res @@ -0,0 +1,10 @@ +{ + fruit = { + apple = { + }, + orange = { + }, + }, + animal = { + }, +} diff --git a/stdex/tab08.toml b/stdex/tab08.toml new file mode 100644 index 0000000..c57a229 --- /dev/null +++ b/stdex/tab08.toml @@ -0,0 +1,8 @@ +# Top-level table begins. +name = "Fido" +breed = "pug" + +# Top-level table ends. +[owner] +name = "Regina Dogman" +member_since = 1999-08-04 diff --git a/stdex/tab08.toml.res b/stdex/tab08.toml.res new file mode 100644 index 0000000..64371ae --- /dev/null +++ b/stdex/tab08.toml.res @@ -0,0 +1,8 @@ +{ + name = "Fido", + breed = "pug", + owner = { + name = "Regina Dogman", + member_since = 1999-08-04, + }, +} diff --git a/stdex/tab09.toml b/stdex/tab09.toml new file mode 100644 index 0000000..d5a2c11 --- /dev/null +++ b/stdex/tab09.toml @@ -0,0 +1,7 @@ +fruit.apple.color = "red" +# Defines a table named fruit +# Defines a table named fruit.apple + +fruit.apple.taste.sweet = true +# Defines a table named fruit.apple.taste +# fruit and fruit.apple were already created diff --git a/stdex/tab09.toml.res b/stdex/tab09.toml.res new file mode 100644 index 0000000..9346469 --- /dev/null +++ b/stdex/tab09.toml.res @@ -0,0 +1,10 @@ +{ + fruit = { + apple = { + color = "red", + taste = { + sweet = true, + }, + }, + }, +} diff --git a/stdex/tab10.toml b/stdex/tab10.toml new file mode 100644 index 0000000..9eac5f6 --- /dev/null +++ b/stdex/tab10.toml @@ -0,0 +1,5 @@ +[fruit] +apple.color = "red" +apple.taste.sweet = true + +[fruit.apple] # INVALID diff --git a/stdex/tab10.toml.res b/stdex/tab10.toml.res new file mode 100644 index 0000000..eb9b163 --- /dev/null +++ b/stdex/tab10.toml.res @@ -0,0 +1 @@ +ERROR: line 5: key exists diff --git a/stdex/tab11.toml b/stdex/tab11.toml new file mode 100644 index 0000000..ae2d65b --- /dev/null +++ b/stdex/tab11.toml @@ -0,0 +1,9 @@ +[fruit] +apple.color = "red" +apple.taste.sweet = true + +# [fruit.apple] # INVALID +# [fruit.apple.taste] # INVALID + +[fruit.apple.texture] # you can add sub-tables +smooth = true diff --git a/stdex/tab11.toml.res b/stdex/tab11.toml.res new file mode 100644 index 0000000..914481b --- /dev/null +++ b/stdex/tab11.toml.res @@ -0,0 +1,13 @@ +{ + fruit = { + apple = { + color = "red", + taste = { + sweet = true, + }, + texture = { + smooth = true, + }, + }, + }, +} diff --git a/stdex/ts1.toml b/stdex/ts1.toml new file mode 100644 index 0000000..9c15c6b --- /dev/null +++ b/stdex/ts1.toml @@ -0,0 +1,5 @@ +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-27T00:32:00.01-07:00 + diff --git a/stdex/ts1.toml.res b/stdex/ts1.toml.res new file mode 100644 index 0000000..2df13b0 --- /dev/null +++ b/stdex/ts1.toml.res @@ -0,0 +1,6 @@ +{ + odt1 = 1979-05-27T07:32:00Z, + odt2 = 1979-05-27T00:32:00-07:00, + odt3 = 1979-05-27T00:32:00.999-07:00, + odt4 = 1979-05-27T00:32:00.010-07:00, +} diff --git a/stdex/ts2.toml b/stdex/ts2.toml new file mode 100644 index 0000000..da9bd3b --- /dev/null +++ b/stdex/ts2.toml @@ -0,0 +1 @@ +odt4 = 1979-05-27 07:32:00Z diff --git a/stdex/ts2.toml.res b/stdex/ts2.toml.res new file mode 100644 index 0000000..1b5d0a8 --- /dev/null +++ b/stdex/ts2.toml.res @@ -0,0 +1,3 @@ +{ + odt4 = 1979-05-27T07:32:00Z, +} diff --git a/stdex/ts3.toml b/stdex/ts3.toml new file mode 100644 index 0000000..38700d2 --- /dev/null +++ b/stdex/ts3.toml @@ -0,0 +1,2 @@ +ldt1 = 1979-05-27T07:32:00 +ldt2 = 1979-05-27T00:32:00.999999 diff --git a/stdex/ts3.toml.res b/stdex/ts3.toml.res new file mode 100644 index 0000000..54a0438 --- /dev/null +++ b/stdex/ts3.toml.res @@ -0,0 +1,4 @@ +{ + ldt1 = 1979-05-27T07:32:00, + ldt2 = 1979-05-27T00:32:00.999, +} diff --git a/stdex/ts4.toml b/stdex/ts4.toml new file mode 100644 index 0000000..6670e5d --- /dev/null +++ b/stdex/ts4.toml @@ -0,0 +1 @@ +ld1 = 1979-05-27 diff --git a/stdex/ts4.toml.res b/stdex/ts4.toml.res new file mode 100644 index 0000000..473b6c2 --- /dev/null +++ b/stdex/ts4.toml.res @@ -0,0 +1,3 @@ +{ + ld1 = 1979-05-27, +} diff --git a/stdex/ts5.toml b/stdex/ts5.toml new file mode 100644 index 0000000..dbd058a --- /dev/null +++ b/stdex/ts5.toml @@ -0,0 +1,2 @@ +lt1 = 07:32:00 +lt2 = 00:32:00.999999 diff --git a/stdex/ts5.toml.res b/stdex/ts5.toml.res new file mode 100644 index 0000000..cd5b62e --- /dev/null +++ b/stdex/ts5.toml.res @@ -0,0 +1,4 @@ +{ + lt1 = 07:32:00, + lt2 = 00:32:00.999, +} diff --git a/test1/.gitignore b/test1/.gitignore new file mode 100644 index 0000000..f39c742 --- /dev/null +++ b/test1/.gitignore @@ -0,0 +1,3 @@ +/goworkspace +/toml-test +/toml-test-decoder diff --git a/test1/README.md b/test1/README.md new file mode 100644 index 0000000..1a83616 --- /dev/null +++ b/test1/README.md @@ -0,0 +1,22 @@ +How to run the tests +=== + +``` +% bash build.sh +% bash run.sh +Test: array-mixed-types-arrays-and-ints (invalid) + +Expected an error, but no error was reported. +------------------------------------------------------------------------------- +Test: array-mixed-types-ints-and-floats (invalid) + +Expected an error, but no error was reported. +------------------------------------------------------------------------------- +Test: array-mixed-types-strings-and-ints (invalid) + +Expected an error, but no error was reported. + +129 passed, 3 failed +``` + +Note: toml version 1.0 allows mixed types in arrays. diff --git a/test1/build.sh b/test1/build.sh new file mode 100644 index 0000000..645a271 --- /dev/null +++ b/test1/build.sh @@ -0,0 +1,9 @@ + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +mkdir -p $DIR/goworkspace +export GOPATH=$DIR/goworkspace +go get github.com/BurntSushi/toml-test@latest # install test suite +go install github.com/BurntSushi/toml/cmd/toml-test-decoder@latest # e.g., install my parser +cp $GOPATH/bin/* . + diff --git a/test1/extra/array_of_tables.toml b/test1/extra/array_of_tables.toml new file mode 100644 index 0000000..86ed1f0 --- /dev/null +++ b/test1/extra/array_of_tables.toml @@ -0,0 +1 @@ +x = [ {'a'= 1}, {'a'= 2} ] diff --git a/test1/extra/inline_array.toml b/test1/extra/inline_array.toml new file mode 100644 index 0000000..a0a971d --- /dev/null +++ b/test1/extra/inline_array.toml @@ -0,0 +1 @@ +x = [1,2,3] diff --git a/test1/extra/inline_table.toml b/test1/extra/inline_table.toml new file mode 100644 index 0000000..7673f2e --- /dev/null +++ b/test1/extra/inline_table.toml @@ -0,0 +1 @@ +x = {'a'= 1, 'b'= 2 } diff --git a/test1/run.sh b/test1/run.sh new file mode 100644 index 0000000..356a8cb --- /dev/null +++ b/test1/run.sh @@ -0,0 +1,5 @@ +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +rm -f tests +ln -s ./goworkspace/pkg/mod/github.com/\!burnt\!sushi/toml-test@v0.1.0/tests +./toml-test ../toml_json diff --git a/test2/.gitignore b/test2/.gitignore new file mode 100644 index 0000000..21335a0 --- /dev/null +++ b/test2/.gitignore @@ -0,0 +1 @@ +/toml-spec-tests \ No newline at end of file diff --git a/test2/Note.txt b/test2/Note.txt new file mode 100644 index 0000000..6ca7347 --- /dev/null +++ b/test2/Note.txt @@ -0,0 +1,6 @@ +Note that we utilize the 'jq' command to normalize json files for comparison. Depending the +verson of jq on your system, some tests may generate error that looks like this: + + parse error: Exceeds depth limit for parsing at line 1 + +This is an error in 'jq', and it does not indicate that the tests themselves fail. diff --git a/test2/build.sh b/test2/build.sh new file mode 100644 index 0000000..74da4d0 --- /dev/null +++ b/test2/build.sh @@ -0,0 +1,6 @@ +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +[ -d toml-spec-tests ] || git clone https://github.com/cktan/toml-spec-tests.git + diff --git a/test2/run.sh b/test2/run.sh new file mode 100644 index 0000000..e1d93df --- /dev/null +++ b/test2/run.sh @@ -0,0 +1,42 @@ +if ! (which jq >& /dev/null); then + echo "ERROR: please install the 'jq' utility" + exit 1 +fi + +# +# POSITIVE tests +# +for i in toml-spec-tests/values/*.toml; do + fname="$i" + ext="${fname##*.}" + fname="${fname%.*}" + echo -n $fname ' ' + res='[OK]' + if (../toml_json $fname.toml >& $fname.json.out); then + jq -S . $fname.json.out > t.json + mv t.json $fname.json.out + if [ -f $fname.json ]; then + if ! (diff $fname.json $fname.json.out >& /dev/null); then + res='[FAILED]' + else + rm -f $fname.json.out + fi + else + res='[??]' + fi + fi + echo ... $res +done + + +# +# NEGATIVE tests +# +for i in toml-spec-tests/errors/*.toml; do + echo -n $i ' ' + res='[OK]' + if (../toml_json $i >& $i.json.out); then + res='[FAILED]' + fi + echo ... $res +done diff --git a/toml.c b/toml.c new file mode 100644 index 0000000..2f4dc03 --- /dev/null +++ b/toml.c @@ -0,0 +1,2379 @@ +/* + + MIT License + + Copyright (c) CK Tan + https://github.com/cktan/tomlc99 + + 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. + +*/ +#define _POSIX_C_SOURCE 200809L +#include "toml.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static void *(*ppmalloc)(size_t) = malloc; +static void (*ppfree)(void *) = free; + +void toml_set_memutil(void *(*xxmalloc)(size_t), void (*xxfree)(void *)) { + if (xxmalloc) + ppmalloc = xxmalloc; + if (xxfree) + ppfree = xxfree; +} + +#define MALLOC(a) ppmalloc(a) +#define FREE(a) ppfree(a) + +#define malloc(x) error - forbidden - use MALLOC instead +#define free(x) error - forbidden - use FREE instead +#define calloc(x, y) error - forbidden - use CALLOC instead + +static void *CALLOC(size_t nmemb, size_t sz) { + int nb = sz * nmemb; + void *p = MALLOC(nb); + if (p) { + memset(p, 0, nb); + } + return p; +} + +// some old platforms define strdup macro -- drop it. +#undef strdup +#define strdup(x) error - forbidden - use STRDUP instead + +static char *STRDUP(const char *s) { + int len = strlen(s); + char *p = MALLOC(len + 1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +// some old platforms define strndup macro -- drop it. +#undef strndup +#define strndup(x) error - forbiden - use STRNDUP instead + +static char *STRNDUP(const char *s, size_t n) { + size_t len = strnlen(s, n); + char *p = MALLOC(len + 1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +/** + * Convert a char in utf8 into UCS, and store it in *ret. + * Return #bytes consumed or -1 on failure. + */ +int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret) { + const unsigned char *buf = (const unsigned char *)orig; + unsigned i = *buf++; + int64_t v; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (0 == (i >> 7)) { + if (len < 1) + return -1; + v = i; + return *ret = v, 1; + } + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (0x6 == (i >> 5)) { + if (len < 2) + return -1; + v = i & 0x1f; + for (int j = 0; j < 1; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (0xE == (i >> 4)) { + if (len < 3) + return -1; + v = i & 0x0F; + for (int j = 0; j < 2; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x1E == (i >> 3)) { + if (len < 4) + return -1; + v = i & 0x07; + for (int j = 0; j < 3; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x3E == (i >> 2)) { + if (len < 5) + return -1; + v = i & 0x03; + for (int j = 0; j < 4; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x7e == (i >> 1)) { + if (len < 6) + return -1; + v = i & 0x01; + for (int j = 0; j < 5; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + return -1; +} + +/** + * Convert a UCS char to utf8 code, and return it in buf. + * Return #bytes used in buf to encode the char, or + * -1 on error. + */ +int toml_ucs_to_utf8(int64_t code, char buf[6]) { + /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 + */ + /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well + * as 0xfffe and 0xffff (UCS noncharacters) should not appear in + * conforming UTF-8 streams. + */ + if (0xd800 <= code && code <= 0xdfff) + return -1; + if (0xfffe <= code && code <= 0xffff) + return -1; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (code < 0) + return -1; + if (code <= 0x7F) { + buf[0] = (unsigned char)code; + return 1; + } + + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (code <= 0x000007FF) { + buf[0] = (unsigned char) (0xc0 | (code >> 6)); + buf[1] = (unsigned char) (0x80 | (code & 0x3f)); + return 2; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x0000FFFF) { + buf[0] = (unsigned char) (0xe0 | (code >> 12)); + buf[1] = (unsigned char) (0x80 | ((code >> 6) & 0x3f)); + buf[2] = (unsigned char) (0x80 | (code & 0x3f)); + return 3; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x001FFFFF) { + buf[0] = (unsigned char) (0xf0 | (code >> 18)); + buf[1] = (unsigned char) (0x80 | ((code >> 12) & 0x3f)); + buf[2] = (unsigned char) (0x80 | ((code >> 6) & 0x3f)); + buf[3] = (unsigned char) (0x80 | (code & 0x3f)); + return 4; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x03FFFFFF) { + buf[0] = (unsigned char) (0xf8 | (code >> 24)); + buf[1] = (unsigned char) (0x80 | ((code >> 18) & 0x3f)); + buf[2] = (unsigned char) (0x80 | ((code >> 12) & 0x3f)); + buf[3] = (unsigned char) (0x80 | ((code >> 6) & 0x3f)); + buf[4] = (unsigned char) (0x80 | (code & 0x3f)); + return 5; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x7FFFFFFF) { + buf[0] = (unsigned char) (0xfc | (code >> 30)); + buf[1] = (unsigned char) (0x80 | ((code >> 24) & 0x3f)); + buf[2] = (unsigned char) (0x80 | ((code >> 18) & 0x3f)); + buf[3] = (unsigned char) (0x80 | ((code >> 12) & 0x3f)); + buf[4] = (unsigned char) (0x80 | ((code >> 6) & 0x3f)); + buf[5] = (unsigned char) (0x80 | (code & 0x3f)); + return 6; + } + + return -1; +} + +/* + * TOML has 3 data structures: value, array, table. + * Each of them can have identification key. + */ +typedef struct toml_keyval_t toml_keyval_t; +struct toml_keyval_t { + const char *key; /* key to this value */ + const char *val; /* the raw value */ +}; + +typedef struct toml_arritem_t toml_arritem_t; +struct toml_arritem_t { + int valtype; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, + 'D'ate, 'T'imestamp */ + char *val; + toml_array_t *arr; + toml_table_t *tab; +}; + +struct toml_array_t { + const char *key; /* key to this array */ + int kind; /* element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed */ + int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, + 'D'ate, 'T'imestamp, 'm'ixed */ + + int nitem; /* number of elements */ + toml_arritem_t *item; +}; + +struct toml_table_t { + const char *key; /* key to this table */ + bool implicit; /* table was created implicitly */ + bool readonly; /* no more modification allowed */ + + /* key-values in the table */ + int nkval; + toml_keyval_t **kval; + + /* arrays in the table */ + int narr; + toml_array_t **arr; + + /* tables in the table */ + int ntab; + toml_table_t **tab; +}; + +static inline void xfree(const void *x) { + if (x) + FREE((void *)(intptr_t)x); +} + +enum tokentype_t { + INVALID, + DOT, + COMMA, + EQUAL, + LBRACE, + RBRACE, + NEWLINE, + LBRACKET, + RBRACKET, + STRING, +}; +typedef enum tokentype_t tokentype_t; + +typedef struct token_t token_t; +struct token_t { + tokentype_t tok; + int lineno; + char *ptr; /* points into context->start */ + int len; + int eof; +}; + +typedef struct context_t context_t; +struct context_t { + char *start; + char *stop; + char *errbuf; + int errbufsz; + + token_t tok; + toml_table_t *root; + toml_table_t *curtab; + + struct { + int top; + char *key[10]; + token_t tok[10]; + } tpath; +}; + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define FLINE __FILE__ ":" TOSTRING(__LINE__) + +static int next_token(context_t *ctx, int dotisspecial); + +/* + Error reporting. Call when an error is detected. Always return -1. +*/ +static int e_outofmemory(context_t *ctx, const char *fline) { + snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline); + return -1; +} + +static int e_internal(context_t *ctx, const char *fline) { + snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline); + return -1; +} + +static int e_syntax(context_t *ctx, int lineno, const char *msg) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static int e_badkey(context_t *ctx, int lineno) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno); + return -1; +} + +static int e_keyexists(context_t *ctx, int lineno) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno); + return -1; +} + +static int e_forbid(context_t *ctx, int lineno, const char *msg) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static void *expand(void *p, int sz, int newsz) { + void *s = MALLOC(newsz); + if (!s) + return 0; + + memcpy(s, p, sz); + FREE(p); + return s; +} + +static void **expand_ptrarr(void **p, int n) { + void **s = MALLOC((n + 1) * sizeof(void *)); + if (!s) + return 0; + + s[n] = 0; + memcpy(s, p, n * sizeof(void *)); + FREE(p); + return s; +} + +static toml_arritem_t *expand_arritem(toml_arritem_t *p, int n) { + toml_arritem_t *pp = expand(p, n * sizeof(*p), (n + 1) * sizeof(*p)); + if (!pp) + return 0; + + memset(&pp[n], 0, sizeof(pp[n])); + return pp; +} + +static char *norm_lit_str(const char *src, int srclen, int multiline, + char *errbuf, int errbufsz) { + char *dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char *sp = src; + const char *sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char *x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) + break; + + ch = *sp++; + /* control characters other than tab is not allowed */ + if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || (ch == 0x7f)) { + if (!(multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + } + + dst[off++] = 0; + return dst; +} + +/* + * Convert src to raw unescaped utf-8 string. + * Returns NULL if error with errmsg in errbuf. + */ +static char *norm_basic_str(const char *src, int srclen, int multiline, + char *errbuf, int errbufsz) { + char *dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char *sp = src; + const char *sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char *x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) + break; + + ch = *sp++; + if (ch != '\\') { + /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F + */ + if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || + (ch == 0x7f)) { + if (!(multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + continue; + } + + /* ch was backslash. we expect the escape char. */ + if (sp >= sq) { + snprintf(errbuf, errbufsz, "last backslash is invalid"); + xfree(dst); + return 0; + } + + /* for multi-line, we want to kill line-ending-backslash ... */ + if (multiline) { + + // if there is only whitespace after the backslash ... + if (sp[strspn(sp, " \t\r")] == '\n') { + /* skip all the following whitespaces */ + sp += strspn(sp, " \t\r\n"); + continue; + } + } + + /* get the escaped char */ + ch = *sp++; + switch (ch) { + case 'u': + case 'U': { + int64_t ucs = 0; + int nhex = (ch == 'u' ? 4 : 8); + for (int i = 0; i < nhex; i++) { + if (sp >= sq) { + snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex); + xfree(dst); + return 0; + } + ch = *sp++; + int v = ('0' <= ch && ch <= '9') + ? ch - '0' + : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1); + if (-1 == v) { + snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U"); + xfree(dst); + return 0; + } + ucs = ucs * 16 + v; + } + int n = toml_ucs_to_utf8(ucs, &dst[off]); + if (-1 == n) { + snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U"); + xfree(dst); + return 0; + } + off += n; + } + continue; + + case 'b': + ch = '\b'; + break; + case 't': + ch = '\t'; + break; + case 'n': + ch = '\n'; + break; + case 'f': + ch = '\f'; + break; + case 'r': + ch = '\r'; + break; + case '"': + ch = '"'; + break; + case '\\': + ch = '\\'; + break; + default: + snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch); + xfree(dst); + return 0; + } + + dst[off++] = ch; + } + + // Cap with NUL and return it. + dst[off++] = 0; + return dst; +} + +/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */ +static char *normalize_key(context_t *ctx, token_t strtok) { + const char *sp = strtok.ptr; + const char *sq = strtok.ptr + strtok.len; + int lineno = strtok.lineno; + char *ret; + int ch = *sp; + char ebuf[80]; + + /* handle quoted string */ + if (ch == '\'' || ch == '\"') { + /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */ + int multiline = 0; + if (sp[1] == ch && sp[2] == ch) { + sp += 3, sq -= 3; + multiline = 1; + } else + sp++, sq--; + + if (ch == '\'') { + /* for single quote, take it verbatim. */ + if (!(ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + } else { + /* for double quote, we need to normalize */ + ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf)); + if (!ret) { + e_syntax(ctx, lineno, ebuf); + return 0; + } + } + + /* newlines are not allowed in keys */ + if (strchr(ret, '\n')) { + xfree(ret); + e_badkey(ctx, lineno); + return 0; + } + return ret; + } + + /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */ + const char *xp; + for (xp = sp; xp != sq; xp++) { + int k = *xp; + if (isalnum(k)) + continue; + if (k == '_' || k == '-') + continue; + e_badkey(ctx, lineno); + return 0; + } + + /* dup and return it */ + if (!(ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + return ret; +} + +/* + * Look up key in tab. Return 0 if not found, or + * 'v'alue, 'a'rray or 't'able depending on the element. + */ +static int check_key(toml_table_t *tab, const char *key, + toml_keyval_t **ret_val, toml_array_t **ret_arr, + toml_table_t **ret_tab) { + int i; + void *dummy; + + if (!ret_tab) + ret_tab = (toml_table_t **)&dummy; + if (!ret_arr) + ret_arr = (toml_array_t **)&dummy; + if (!ret_val) + ret_val = (toml_keyval_t **)&dummy; + + *ret_tab = 0; + *ret_arr = 0; + *ret_val = 0; + + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) { + *ret_val = tab->kval[i]; + return 'v'; + } + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) { + *ret_arr = tab->arr[i]; + return 'a'; + } + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) { + *ret_tab = tab->tab[i]; + return 't'; + } + } + return 0; +} + +static int key_kind(toml_table_t *tab, const char *key) { + return check_key(tab, key, 0, 0, 0); +} + +/* Create a keyval in the table. + */ +static toml_keyval_t *create_keyval_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out. */ + toml_keyval_t *dest = 0; + if (key_kind(tab, newkey)) { + xfree(newkey); + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new entry */ + int n = tab->nkval; + toml_keyval_t **base; + if (0 == (base = (toml_keyval_t **)expand_ptrarr((void **)tab->kval, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->kval = base; + + if (0 == (base[n] = (toml_keyval_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->kval[tab->nkval++]; + + /* save the key in the new value struct */ + dest->key = newkey; + return dest; +} + +/* Create a table in the table. + */ +static toml_table_t *create_keytable_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out */ + toml_table_t *dest = 0; + if (check_key(tab, newkey, 0, 0, &dest)) { + xfree(newkey); /* don't need this anymore */ + + /* special case: if table exists, but was created implicitly ... */ + if (dest && dest->implicit) { + /* we make it explicit now, and simply return it. */ + dest->implicit = false; + return dest; + } + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* create a new table entry */ + int n = tab->ntab; + toml_table_t **base; + if (0 == (base = (toml_table_t **)expand_ptrarr((void **)tab->tab, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->tab = base; + + if (0 == (base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->tab[tab->ntab++]; + + /* save the key in the new table struct */ + dest->key = newkey; + return dest; +} + +/* Create an array in the table. + */ +static toml_array_t *create_keyarray_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok, char kind) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out */ + if (key_kind(tab, newkey)) { + xfree(newkey); /* don't need this anymore */ + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new array entry */ + int n = tab->narr; + toml_array_t **base; + if (0 == (base = (toml_array_t **)expand_ptrarr((void **)tab->arr, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->arr = base; + + if (0 == (base[n] = (toml_array_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t *dest = tab->arr[tab->narr++]; + + /* save the key in the new array struct */ + dest->key = newkey; + dest->kind = kind; + return dest; +} + +static toml_arritem_t *create_value_in_array(context_t *ctx, + toml_array_t *parent) { + const int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + parent->item = base; + parent->nitem++; + return &parent->item[n]; +} + +/* Create an array in an array + */ +static toml_array_t *create_array_in_array(context_t *ctx, + toml_array_t *parent) { + const int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t *ret = (toml_array_t *)CALLOC(1, sizeof(toml_array_t)); + if (!ret) { + e_outofmemory(ctx, FLINE); + return 0; + } + base[n].arr = ret; + parent->item = base; + parent->nitem++; + return ret; +} + +/* Create a table in an array + */ +static toml_table_t *create_table_in_array(context_t *ctx, + toml_array_t *parent) { + int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + toml_table_t *ret = (toml_table_t *)CALLOC(1, sizeof(toml_table_t)); + if (!ret) { + e_outofmemory(ctx, FLINE); + return 0; + } + base[n].tab = ret; + parent->item = base; + parent->nitem++; + return ret; +} + +static int skip_newlines(context_t *ctx, int isdotspecial) { + while (ctx->tok.tok == NEWLINE) { + if (next_token(ctx, isdotspecial)) + return -1; + if (ctx->tok.eof) + break; + } + return 0; +} + +static int parse_keyval(context_t *ctx, toml_table_t *tab); + +static inline int eat_token(context_t *ctx, tokentype_t typ, int isdotspecial, + const char *fline) { + if (ctx->tok.tok != typ) + return e_internal(ctx, fline); + + if (next_token(ctx, isdotspecial)) + return -1; + + return 0; +} + +/* We are at '{ ... }'. + * Parse the table. + */ +static int parse_inline_table(context_t *ctx, toml_table_t *tab) { + if (eat_token(ctx, LBRACE, 1, FLINE)) + return -1; + + for (;;) { + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, + "newline not allowed in inline table"); + + /* until } */ + if (ctx->tok.tok == RBRACE) + break; + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, ctx->tok.lineno, "expect a string"); + + if (parse_keyval(ctx, tab)) + return -1; + + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, + "newline not allowed in inline table"); + + /* on comma, continue to scan for next keyval */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 1, FLINE)) + return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACE, 1, FLINE)) + return -1; + + tab->readonly = 1; + + return 0; +} + +static int valtype(const char *val) { + toml_timestamp_t ts; + if (*val == '\'' || *val == '"') + return 's'; + if (0 == toml_rtob(val, 0)) + return 'b'; + if (0 == toml_rtoi(val, 0)) + return 'i'; + if (0 == toml_rtod(val, 0)) + return 'd'; + if (0 == toml_rtots(val, &ts)) { + if (ts.year && ts.hour) + return 'T'; /* timestamp */ + if (ts.year) + return 'D'; /* date */ + return 't'; /* time */ + } + return 'u'; /* unknown */ +} + +/* We are at '[...]' */ +static int parse_array(context_t *ctx, toml_array_t *arr) { + if (eat_token(ctx, LBRACKET, 0, FLINE)) + return -1; + + for (;;) { + if (skip_newlines(ctx, 0)) + return -1; + + /* until ] */ + if (ctx->tok.tok == RBRACKET) + break; + + switch (ctx->tok.tok) { + case STRING: { + /* set array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 'v'; + else if (arr->kind != 'v') + arr->kind = 'm'; + + char *val = ctx->tok.ptr; + int vlen = ctx->tok.len; + + /* make a new value in array */ + toml_arritem_t *newval = create_value_in_array(ctx, arr); + if (!newval) + return e_outofmemory(ctx, FLINE); + + if (!(newval->val = STRNDUP(val, vlen))) + return e_outofmemory(ctx, FLINE); + + newval->valtype = valtype(newval->val); + + /* set array type if this is the first entry */ + if (arr->nitem == 1) + arr->type = newval->valtype; + else if (arr->type != newval->valtype) + arr->type = 'm'; /* mixed */ + + if (eat_token(ctx, STRING, 0, FLINE)) + return -1; + break; + } + + case LBRACKET: { /* [ [array], [array] ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 'a'; + else if (arr->kind != 'a') + arr->kind = 'm'; + + toml_array_t *subarr = create_array_in_array(ctx, arr); + if (!subarr) + return -1; + if (parse_array(ctx, subarr)) + return -1; + break; + } + + case LBRACE: { /* [ {table}, {table} ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 't'; + else if (arr->kind != 't') + arr->kind = 'm'; + + toml_table_t *subtab = create_table_in_array(ctx, arr); + if (!subtab) + return -1; + if (parse_inline_table(ctx, subtab)) + return -1; + break; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + + if (skip_newlines(ctx, 0)) + return -1; + + /* on comma, continue to scan for next element */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 0, FLINE)) + return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + return 0; +} + +/* handle lines like these: + key = "value" + key = [ array ] + key = { table } +*/ +static int parse_keyval(context_t *ctx, toml_table_t *tab) { + if (tab->readonly) { + return e_forbid(ctx, ctx->tok.lineno, + "cannot insert new entry into existing table"); + } + + token_t key = ctx->tok; + if (eat_token(ctx, STRING, 1, FLINE)) + return -1; + + if (ctx->tok.tok == DOT) { + /* handle inline dotted key. + e.g. + physical.color = "orange" + physical.shape = "round" + */ + toml_table_t *subtab = 0; + { + char *subtabstr = normalize_key(ctx, key); + if (!subtabstr) + return -1; + + subtab = toml_table_in(tab, subtabstr); + xfree(subtabstr); + } + if (!subtab) { + subtab = create_keytable_in_table(ctx, tab, key); + if (!subtab) + return -1; + } + if (next_token(ctx, 1)) + return -1; + if (parse_keyval(ctx, subtab)) + return -1; + return 0; + } + + if (ctx->tok.tok != EQUAL) { + return e_syntax(ctx, ctx->tok.lineno, "missing ="); + } + + if (next_token(ctx, 0)) + return -1; + + switch (ctx->tok.tok) { + case STRING: { /* key = "value" */ + toml_keyval_t *keyval = create_keyval_in_table(ctx, tab, key); + if (!keyval) + return -1; + token_t val = ctx->tok; + + assert(keyval->val == 0); + if (!(keyval->val = STRNDUP(val.ptr, val.len))) + return e_outofmemory(ctx, FLINE); + + if (next_token(ctx, 1)) + return -1; + + return 0; + } + + case LBRACKET: { /* key = [ array ] */ + toml_array_t *arr = create_keyarray_in_table(ctx, tab, key, 0); + if (!arr) + return -1; + if (parse_array(ctx, arr)) + return -1; + return 0; + } + + case LBRACE: { /* key = { table } */ + toml_table_t *nxttab = create_keytable_in_table(ctx, tab, key); + if (!nxttab) + return -1; + if (parse_inline_table(ctx, nxttab)) + return -1; + return 0; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + return 0; +} + +typedef struct tabpath_t tabpath_t; +struct tabpath_t { + int cnt; + token_t key[10]; +}; + +/* at [x.y.z] or [[x.y.z]] + * Scan forward and fill tabpath until it enters ] or ]] + * There will be at least one entry on return. + */ +static int fill_tabpath(context_t *ctx) { + int lineno = ctx->tok.lineno; + int i; + + /* clear tpath */ + for (i = 0; i < ctx->tpath.top; i++) { + char **p = &ctx->tpath.key[i]; + xfree(*p); + *p = 0; + } + ctx->tpath.top = 0; + + for (;;) { + if (ctx->tpath.top >= 10) + return e_syntax(ctx, lineno, + "table path is too deep; max allowed is 10."); + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, lineno, "invalid or missing key"); + + char *key = normalize_key(ctx, ctx->tok); + if (!key) + return -1; + ctx->tpath.tok[ctx->tpath.top] = ctx->tok; + ctx->tpath.key[ctx->tpath.top] = key; + ctx->tpath.top++; + + if (next_token(ctx, 1)) + return -1; + + if (ctx->tok.tok == RBRACKET) + break; + + if (ctx->tok.tok != DOT) + return e_syntax(ctx, lineno, "invalid key"); + + if (next_token(ctx, 1)) + return -1; + } + + if (ctx->tpath.top <= 0) + return e_syntax(ctx, lineno, "empty table selector"); + + return 0; +} + +/* Walk tabpath from the root, and create new tables on the way. + * Sets ctx->curtab to the final table. + */ +static int walk_tabpath(context_t *ctx) { + /* start from root */ + toml_table_t *curtab = ctx->root; + + for (int i = 0; i < ctx->tpath.top; i++) { + const char *key = ctx->tpath.key[i]; + + toml_keyval_t *nextval = 0; + toml_array_t *nextarr = 0; + toml_table_t *nexttab = 0; + switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) { + case 't': + /* found a table. nexttab is where we will go next. */ + break; + + case 'a': + /* found an array. nexttab is the last table in the array. */ + if (nextarr->kind != 't') + return e_internal(ctx, FLINE); + + if (nextarr->nitem == 0) + return e_internal(ctx, FLINE); + + nexttab = nextarr->item[nextarr->nitem - 1].tab; + break; + + case 'v': + return e_keyexists(ctx, ctx->tpath.tok[i].lineno); + + default: { /* Not found. Let's create an implicit table. */ + int n = curtab->ntab; + toml_table_t **base = + (toml_table_t **)expand_ptrarr((void **)curtab->tab, n); + if (0 == base) + return e_outofmemory(ctx, FLINE); + + curtab->tab = base; + + if (0 == (base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n])))) + return e_outofmemory(ctx, FLINE); + + if (0 == (base[n]->key = STRDUP(key))) + return e_outofmemory(ctx, FLINE); + + nexttab = curtab->tab[curtab->ntab++]; + + /* tabs created by walk_tabpath are considered implicit */ + nexttab->implicit = true; + } break; + } + + /* switch to next tab */ + curtab = nexttab; + } + + /* save it */ + ctx->curtab = curtab; + + return 0; +} + +/* handle lines like [x.y.z] or [[x.y.z]] */ +static int parse_select(context_t *ctx) { + assert(ctx->tok.tok == LBRACKET); + + /* true if [[ */ + int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '['); + /* need to detect '[[' on our own because next_token() will skip whitespace, + and '[ [' would be taken as '[[', which is wrong. */ + + /* eat [ or [[ */ + if (eat_token(ctx, LBRACKET, 1, FLINE)) + return -1; + if (llb) { + assert(ctx->tok.tok == LBRACKET); + if (eat_token(ctx, LBRACKET, 1, FLINE)) + return -1; + } + + if (fill_tabpath(ctx)) + return -1; + + /* For [x.y.z] or [[x.y.z]], remove z from tpath. + */ + token_t z = ctx->tpath.tok[ctx->tpath.top - 1]; + xfree(ctx->tpath.key[ctx->tpath.top - 1]); + ctx->tpath.top--; + + /* set up ctx->curtab */ + if (walk_tabpath(ctx)) + return -1; + + if (!llb) { + /* [x.y.z] -> create z = {} in x.y */ + toml_table_t *curtab = create_keytable_in_table(ctx, ctx->curtab, z); + if (!curtab) + return -1; + ctx->curtab = curtab; + } else { + /* [[x.y.z]] -> create z = [] in x.y */ + toml_array_t *arr = 0; + { + char *zstr = normalize_key(ctx, z); + if (!zstr) + return -1; + arr = toml_array_in(ctx->curtab, zstr); + xfree(zstr); + } + if (!arr) { + arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't'); + if (!arr) + return -1; + } + if (arr->kind != 't') + return e_syntax(ctx, z.lineno, "array mismatch"); + + /* add to z[] */ + toml_table_t *dest; + { + toml_table_t *t = create_table_in_array(ctx, arr); + if (!t) + return -1; + + if (0 == (t->key = STRDUP("__anon__"))) + return e_outofmemory(ctx, FLINE); + + dest = t; + } + + ctx->curtab = dest; + } + + if (ctx->tok.tok != RBRACKET) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]"); + } + if (llb) { + if (!(ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]]"); + } + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + + if (ctx->tok.tok != NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]"); + + return 0; +} + +toml_table_t *toml_parse(char *conf, char *errbuf, int errbufsz) { + context_t ctx; + + // clear errbuf + if (errbufsz <= 0) + errbufsz = 0; + if (errbufsz > 0) + errbuf[0] = 0; + + // init context + memset(&ctx, 0, sizeof(ctx)); + ctx.start = conf; + ctx.stop = ctx.start + strlen(conf); + ctx.errbuf = errbuf; + ctx.errbufsz = errbufsz; + + // start with an artificial newline of length 0 + ctx.tok.tok = NEWLINE; + ctx.tok.lineno = 1; + ctx.tok.ptr = conf; + ctx.tok.len = 0; + + // make a root table + if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) { + e_outofmemory(&ctx, FLINE); + // Do not goto fail, root table not set up yet + return 0; + } + + // set root as default table + ctx.curtab = ctx.root; + + /* Scan forward until EOF */ + for (token_t tok = ctx.tok; !tok.eof; tok = ctx.tok) { + switch (tok.tok) { + + case NEWLINE: + if (next_token(&ctx, 1)) + goto fail; + break; + + case STRING: + if (parse_keyval(&ctx, ctx.curtab)) + goto fail; + + if (ctx.tok.tok != NEWLINE) { + e_syntax(&ctx, ctx.tok.lineno, "extra chars after value"); + goto fail; + } + + if (eat_token(&ctx, NEWLINE, 1, FLINE)) + goto fail; + break; + + case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */ + if (parse_select(&ctx)) + goto fail; + break; + + default: + e_syntax(&ctx, tok.lineno, "syntax error"); + goto fail; + } + } + + /* success */ + for (int i = 0; i < ctx.tpath.top; i++) + xfree(ctx.tpath.key[i]); + return ctx.root; + +fail: + // Something bad has happened. Free resources and return error. + for (int i = 0; i < ctx.tpath.top; i++) + xfree(ctx.tpath.key[i]); + toml_free(ctx.root); + return 0; +} + +toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz) { + int bufsz = 0; + char *buf = 0; + int off = 0; + + /* read from fp into buf */ + while (!feof(fp)) { + + if (off == bufsz) { + int xsz = bufsz + 1000; + char *x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + + errno = 0; + int n = fread(buf + off, 1, bufsz - off, fp); + if (ferror(fp)) { + snprintf(errbuf, errbufsz, "%s", + errno ? strerror(errno) : "Error reading file"); + xfree(buf); + return 0; + } + off += n; + } + + /* tag on a NUL to cap the string */ + if (off == bufsz) { + int xsz = bufsz + 1; + char *x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + buf[off] = 0; + + /* parse it, cleanup and finish */ + toml_table_t *ret = toml_parse(buf, errbuf, errbufsz); + xfree(buf); + return ret; +} + +static void xfree_kval(toml_keyval_t *p) { + if (!p) + return; + xfree(p->key); + xfree(p->val); + xfree(p); +} + +static void xfree_tab(toml_table_t *p); + +static void xfree_arr(toml_array_t *p) { + if (!p) + return; + + xfree(p->key); + const int n = p->nitem; + for (int i = 0; i < n; i++) { + toml_arritem_t *a = &p->item[i]; + if (a->val) + xfree(a->val); + else if (a->arr) + xfree_arr(a->arr); + else if (a->tab) + xfree_tab(a->tab); + } + xfree(p->item); + xfree(p); +} + +static void xfree_tab(toml_table_t *p) { + int i; + + if (!p) + return; + + xfree(p->key); + + for (i = 0; i < p->nkval; i++) + xfree_kval(p->kval[i]); + xfree(p->kval); + + for (i = 0; i < p->narr; i++) + xfree_arr(p->arr[i]); + xfree(p->arr); + + for (i = 0; i < p->ntab; i++) + xfree_tab(p->tab[i]); + xfree(p->tab); + + xfree(p); +} + +void toml_free(toml_table_t *tab) { xfree_tab(tab); } + +static void set_token(context_t *ctx, tokentype_t tok, int lineno, char *ptr, + int len) { + token_t t; + t.tok = tok; + t.lineno = lineno; + t.ptr = ptr; + t.len = len; + t.eof = 0; + ctx->tok = t; +} + +static void set_eof(context_t *ctx, int lineno) { + set_token(ctx, NEWLINE, lineno, ctx->stop, 0); + ctx->tok.eof = 1; +} + +/* Scan p for n digits compositing entirely of [0-9] */ +static int scan_digits(const char *p, int n) { + int ret = 0; + for (; n > 0 && isdigit(*p); n--, p++) { + ret = 10 * ret + (*p - '0'); + } + return n ? -1 : ret; +} + +static int scan_date(const char *p, int *YY, int *MM, int *DD) { + int year, month, day; + year = scan_digits(p, 4); + month = (year >= 0 && p[4] == '-') ? scan_digits(p + 5, 2) : -1; + day = (month >= 0 && p[7] == '-') ? scan_digits(p + 8, 2) : -1; + if (YY) + *YY = year; + if (MM) + *MM = month; + if (DD) + *DD = day; + return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1; +} + +static int scan_time(const char *p, int *hh, int *mm, int *ss) { + int hour, minute, second; + hour = scan_digits(p, 2); + minute = (hour >= 0 && p[2] == ':') ? scan_digits(p + 3, 2) : -1; + second = (minute >= 0 && p[5] == ':') ? scan_digits(p + 6, 2) : -1; + if (hh) + *hh = hour; + if (mm) + *mm = minute; + if (ss) + *ss = second; + return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1; +} + +static int scan_string(context_t *ctx, char *p, int lineno, int dotisspecial) { + char *orig = p; + if (0 == strncmp(p, "'''", 3)) { + char *q = p + 3; + + while (1) { + q = strstr(q, "'''"); + if (0 == q) { + return e_syntax(ctx, lineno, "unterminated triple-s-quote"); + } + while (q[3] == '\'') + q++; + break; + } + + set_token(ctx, STRING, lineno, orig, q + 3 - orig); + return 0; + } + + if (0 == strncmp(p, "\"\"\"", 3)) { + char *q = p + 3; + + while (1) { + q = strstr(q, "\"\"\""); + if (0 == q) { + return e_syntax(ctx, lineno, "unterminated triple-d-quote"); + } + if (q[-1] == '\\') { + q++; + continue; + } + while (q[3] == '\"') + q++; + break; + } + + // the string is [p+3, q-1] + + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p += 3; p < q; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) + continue; + if (*p == 'u') { + hexreq = 4; + continue; + } + if (*p == 'U') { + hexreq = 8; + continue; + } + if (p[strspn(p, " \t\r")] == '\n') + continue; /* allow for line ending backslash */ + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) + continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { + escape = 1; + continue; + } + } + if (escape) + return e_syntax(ctx, lineno, "expect an escape char"); + if (hexreq) + return e_syntax(ctx, lineno, "expected more hex char"); + + set_token(ctx, STRING, lineno, orig, q + 3 - orig); + return 0; + } + + if ('\'' == *p) { + for (p++; *p && *p != '\n' && *p != '\''; p++) + ; + if (*p != '\'') { + return e_syntax(ctx, lineno, "unterminated s-quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + if ('\"' == *p) { + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p++; *p; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) + continue; + if (*p == 'u') { + hexreq = 4; + continue; + } + if (*p == 'U') { + hexreq = 8; + continue; + } + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) + continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { + escape = 1; + continue; + } + if (*p == '\'') { + if (p[1] == '\'' && p[2] == '\'') { + return e_syntax(ctx, lineno, "triple-s-quote inside string lit"); + } + continue; + } + if (*p == '\n') + break; + if (*p == '"') + break; + } + if (*p != '"') { + return e_syntax(ctx, lineno, "unterminated quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + /* check for timestamp without quotes */ + if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) { + // forward thru the timestamp + p += strspn(p, "0123456789.:+-Tt Zz"); + // squeeze out any spaces at end of string + for (; p[-1] == ' '; p--) + ; + // tokenize + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; + } + + /* literals */ + for (; *p && *p != '\n'; p++) { + int ch = *p; + if (ch == '.' && dotisspecial) + break; + if ('A' <= ch && ch <= 'Z') + continue; + if ('a' <= ch && ch <= 'z') + continue; + if (strchr("0123456789+-_.", ch)) + continue; + break; + } + + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; +} + +static int next_token(context_t *ctx, int dotisspecial) { + int lineno = ctx->tok.lineno; + char *p = ctx->tok.ptr; + int i; + + /* eat this tok */ + for (i = 0; i < ctx->tok.len; i++) { + if (*p++ == '\n') + lineno++; + } + + /* make next tok */ + while (p < ctx->stop) { + /* skip comment. stop just before the \n. */ + if (*p == '#') { + for (p++; p < ctx->stop && *p != '\n'; p++) + ; + continue; + } + + if (dotisspecial && *p == '.') { + set_token(ctx, DOT, lineno, p, 1); + return 0; + } + + switch (*p) { + case ',': + set_token(ctx, COMMA, lineno, p, 1); + return 0; + case '=': + set_token(ctx, EQUAL, lineno, p, 1); + return 0; + case '{': + set_token(ctx, LBRACE, lineno, p, 1); + return 0; + case '}': + set_token(ctx, RBRACE, lineno, p, 1); + return 0; + case '[': + set_token(ctx, LBRACKET, lineno, p, 1); + return 0; + case ']': + set_token(ctx, RBRACKET, lineno, p, 1); + return 0; + case '\n': + set_token(ctx, NEWLINE, lineno, p, 1); + return 0; + case '\r': + case ' ': + case '\t': + /* ignore white spaces */ + p++; + continue; + } + + return scan_string(ctx, p, lineno, dotisspecial); + } + + set_eof(ctx, lineno); + return 0; +} + +const char *toml_key_in(const toml_table_t *tab, int keyidx) { + if (keyidx < tab->nkval) + return tab->kval[keyidx]->key; + + keyidx -= tab->nkval; + if (keyidx < tab->narr) + return tab->arr[keyidx]->key; + + keyidx -= tab->narr; + if (keyidx < tab->ntab) + return tab->tab[keyidx]->key; + + return 0; +} + +int toml_key_exists(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return 1; + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return 1; + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return 1; + } + return 0; +} + +toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return tab->kval[i]->val; + } + return 0; +} + +toml_array_t *toml_array_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return tab->arr[i]; + } + return 0; +} + +toml_table_t *toml_table_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return tab->tab[i]; + } + return 0; +} + +toml_raw_t toml_raw_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0; +} + +char toml_array_kind(const toml_array_t *arr) { return arr->kind; } + +char toml_array_type(const toml_array_t *arr) { + if (arr->kind != 'v') + return 0; + + if (arr->nitem == 0) + return 0; + + return arr->type; +} + +int toml_array_nelem(const toml_array_t *arr) { return arr->nitem; } + +const char *toml_array_key(const toml_array_t *arr) { + return arr ? arr->key : (const char *)NULL; +} + +int toml_table_nkval(const toml_table_t *tab) { return tab->nkval; } + +int toml_table_narr(const toml_table_t *tab) { return tab->narr; } + +int toml_table_ntab(const toml_table_t *tab) { return tab->ntab; } + +const char *toml_table_key(const toml_table_t *tab) { + return tab ? tab->key : (const char *)NULL; +} + +toml_array_t *toml_array_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0; +} + +toml_table_t *toml_table_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0; +} + +static int parse_millisec(const char *p, const char **endp); + +int toml_rtots(toml_raw_t src_, toml_timestamp_t *ret) { + if (!src_) + return -1; + + const char *p = src_; + int must_parse_time = 0; + + memset(ret, 0, sizeof(*ret)); + + int *year = &ret->__buffer.year; + int *month = &ret->__buffer.month; + int *day = &ret->__buffer.day; + int *hour = &ret->__buffer.hour; + int *minute = &ret->__buffer.minute; + int *second = &ret->__buffer.second; + int *millisec = &ret->__buffer.millisec; + + /* parse date YYYY-MM-DD */ + if (0 == scan_date(p, year, month, day)) { + ret->year = year; + ret->month = month; + ret->day = day; + + p += 10; + if (*p) { + // parse the T or space separator + if (*p != 'T' && *p != 't' && *p != ' ') + return -1; + must_parse_time = 1; + p++; + } + } + + /* parse time HH:MM:SS */ + if (0 == scan_time(p, hour, minute, second)) { + ret->hour = hour; + ret->minute = minute; + ret->second = second; + + /* optionally, parse millisec */ + p += 8; + if (*p == '.') { + p++; /* skip '.' */ + const char *qq; + *millisec = parse_millisec(p, &qq); + ret->millisec = millisec; + p = qq; + } + + if (*p) { + /* parse and copy Z */ + char *z = ret->__buffer.z; + ret->z = z; + if (*p == 'Z' || *p == 'z') { + *z++ = 'Z'; + p++; + *z = 0; + + } else if (*p == '+' || *p == '-') { + *z++ = *p++; + + if (!(isdigit(p[0]) && isdigit(p[1]))) + return -1; + *z++ = *p++; + *z++ = *p++; + + if (*p == ':') { + *z++ = *p++; + + if (!(isdigit(p[0]) && isdigit(p[1]))) + return -1; + *z++ = *p++; + *z++ = *p++; + } + + *z = 0; + } + } + } + if (*p != 0) + return -1; + + if (must_parse_time && !ret->hour) + return -1; + + return 0; +} + +/* Raw to boolean */ +int toml_rtob(toml_raw_t src, int *ret_) { + if (!src) + return -1; + int dummy; + int *ret = ret_ ? ret_ : &dummy; + + if (0 == strcmp(src, "true")) { + *ret = 1; + return 0; + } + if (0 == strcmp(src, "false")) { + *ret = 0; + return 0; + } + return -1; +} + +/* Raw to integer */ +int toml_rtoi(toml_raw_t src, int64_t *ret_) { + if (!src) + return -1; + + char buf[100]; + char *p = buf; + char *q = p + sizeof(buf); + const char *s = src; + int base = 0; + int64_t dummy; + int64_t *ret = ret_ ? ret_ : &dummy; + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_100 */ + if (s[0] == '_') + return -1; + + /* if 0* ... */ + if ('0' == s[0]) { + switch (s[1]) { + case 'x': + base = 16; + s += 2; + break; + case 'o': + base = 8; + s += 2; + break; + case 'b': + base = 2; + s += 2; + break; + case '\0': + return *ret = 0, 0; + default: + /* ensure no other digits after it */ + if (s[1]) + return -1; + } + } + + /* just strip underscores and pass to strtoll */ + while (*s && p < q) { + int ch = *s++; + if (ch == '_') { + // disallow '__' + if (s[0] == '_') + return -1; + // numbers cannot end with '_' + if (s[0] == '\0') + return -1; + continue; /* skip _ */ + } + *p++ = ch; + } + + // if not at end-of-string or we ran out of buffer ... + if (*s || p == q) + return -1; + + /* cap with NUL */ + *p = 0; + + /* Run strtoll on buf to get the integer */ + char *endp; + errno = 0; + *ret = strtoll(buf, &endp, base); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod_ex(toml_raw_t src, double *ret_, char *buf, int buflen) { + if (!src) + return -1; + + char *p = buf; + char *q = p + buflen; + const char *s = src; + double dummy; + double *ret = ret_ ? ret_ : &dummy; + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_1.00 */ + if (s[0] == '_') + return -1; + + /* decimal point, if used, must be surrounded by at least one digit on each + * side */ + { + char *dot = strchr(s, '.'); + if (dot) { + if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1])) + return -1; + } + } + + /* zero must be followed by . or 'e', or NUL */ + if (s[0] == '0' && s[1] && !strchr("eE.", s[1])) + return -1; + + /* just strip underscores and pass to strtod */ + while (*s && p < q) { + int ch = *s++; + if (ch == '_') { + // disallow '__' + if (s[0] == '_') + return -1; + // disallow last char '_' + if (s[0] == 0) + return -1; + continue; /* skip _ */ + } + *p++ = ch; + } + if (*s || p == q) + return -1; /* reached end of string or buffer is full? */ + + /* cap with NUL */ + *p = 0; + + /* Run strtod on buf to get the value */ + char *endp; + errno = 0; + *ret = strtod(buf, &endp); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod(toml_raw_t src, double *ret_) { + char buf[100]; + return toml_rtod_ex(src, ret_, buf, sizeof(buf)); +} + +int toml_rtos(toml_raw_t src, char **ret) { + int multiline = 0; + const char *sp; + const char *sq; + + *ret = 0; + if (!src) + return -1; + + int qchar = src[0]; + int srclen = strlen(src); + if (!(qchar == '\'' || qchar == '"')) { + return -1; + } + + // triple quotes? + if (qchar == src[1] && qchar == src[2]) { + multiline = 1; + sp = src + 3; + sq = src + srclen - 3; + /* last 3 chars in src must be qchar */ + if (!(sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar)) + return -1; + + /* skip new line immediate after qchar */ + if (sp[0] == '\n') + sp++; + else if (sp[0] == '\r' && sp[1] == '\n') + sp += 2; + + } else { + sp = src + 1; + sq = src + srclen - 1; + /* last char in src must be qchar */ + if (!(sp <= sq && *sq == qchar)) + return -1; + } + + if (qchar == '\'') { + *ret = norm_lit_str(sp, sq - sp, multiline, 0, 0); + } else { + *ret = norm_basic_str(sp, sq - sp, multiline, 0, 0); + } + + return *ret ? 0 : -1; +} + +toml_datum_t toml_string_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtos(toml_raw_at(arr, idx), &ret.u.s)); + return ret; +} + +toml_datum_t toml_bool_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtob(toml_raw_at(arr, idx), &ret.u.b)); + return ret; +} + +toml_datum_t toml_int_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtoi(toml_raw_at(arr, idx), &ret.u.i)); + return ret; +} + +toml_datum_t toml_double_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtod(toml_raw_at(arr, idx), &ret.u.d)); + return ret; +} + +toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx) { + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtots(toml_raw_at(arr, idx), &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + if (ret.u.ts->year) + ret.u.ts->year = &ret.u.ts->__buffer.year; + if (ret.u.ts->month) + ret.u.ts->month = &ret.u.ts->__buffer.month; + if (ret.u.ts->day) + ret.u.ts->day = &ret.u.ts->__buffer.day; + if (ret.u.ts->hour) + ret.u.ts->hour = &ret.u.ts->__buffer.hour; + if (ret.u.ts->minute) + ret.u.ts->minute = &ret.u.ts->__buffer.minute; + if (ret.u.ts->second) + ret.u.ts->second = &ret.u.ts->__buffer.second; + if (ret.u.ts->millisec) + ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; + if (ret.u.ts->z) + ret.u.ts->z = ret.u.ts->__buffer.z; + } + } + return ret; +} + +toml_datum_t toml_string_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtos(raw, &ret.u.s)); + } + return ret; +} + +toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtob(toml_raw_in(arr, key), &ret.u.b)); + return ret; +} + +toml_datum_t toml_int_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtoi(toml_raw_in(arr, key), &ret.u.i)); + return ret; +} + +toml_datum_t toml_double_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtod(toml_raw_in(arr, key), &ret.u.d)); + return ret; +} + +toml_datum_t toml_timestamp_in(const toml_table_t *arr, const char *key) { + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtots(toml_raw_in(arr, key), &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + if (ret.u.ts->year) + ret.u.ts->year = &ret.u.ts->__buffer.year; + if (ret.u.ts->month) + ret.u.ts->month = &ret.u.ts->__buffer.month; + if (ret.u.ts->day) + ret.u.ts->day = &ret.u.ts->__buffer.day; + if (ret.u.ts->hour) + ret.u.ts->hour = &ret.u.ts->__buffer.hour; + if (ret.u.ts->minute) + ret.u.ts->minute = &ret.u.ts->__buffer.minute; + if (ret.u.ts->second) + ret.u.ts->second = &ret.u.ts->__buffer.second; + if (ret.u.ts->millisec) + ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; + if (ret.u.ts->z) + ret.u.ts->z = ret.u.ts->__buffer.z; + } + } + return ret; +} + +static int parse_millisec(const char *p, const char **endp) { + int ret = 0; + int unit = 100; /* unit in millisec */ + for (; '0' <= *p && *p <= '9'; p++, unit /= 10) { + ret += (*p - '0') * unit; + } + *endp = p; + return ret; +} diff --git a/toml.h b/toml.h new file mode 100644 index 0000000..19dc3d2 --- /dev/null +++ b/toml.h @@ -0,0 +1,175 @@ +/* + MIT License + + Copyright (c) CK Tan + https://github.com/cktan/tomlc99 + + 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. +*/ +#ifndef TOML_H +#define TOML_H + +#ifdef _MSC_VER +#pragma warning(disable: 4996) +#endif + +#include +#include + +#ifdef __cplusplus +#define TOML_EXTERN extern "C" +#else +#define TOML_EXTERN extern +#endif + +typedef struct toml_timestamp_t toml_timestamp_t; +typedef struct toml_table_t toml_table_t; +typedef struct toml_array_t toml_array_t; +typedef struct toml_datum_t toml_datum_t; + +/* Parse a file. Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz); + +/* Parse a string containing the full config. + * Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t *toml_parse(char *conf, /* NUL terminated, please. */ + char *errbuf, int errbufsz); + +/* Free the table returned by toml_parse() or toml_parse_file(). Once + * this function is called, any handles accessed through this tab + * directly or indirectly are no longer valid. + */ +TOML_EXTERN void toml_free(toml_table_t *tab); + +/* Timestamp types. The year, month, day, hour, minute, second, z + * fields may be NULL if they are not relevant. e.g. In a DATE + * type, the hour, minute, second and z fields will be NULLs. + */ +struct toml_timestamp_t { + struct { /* internal. do not use. */ + int year, month, day; + int hour, minute, second, millisec; + char z[10]; + } __buffer; + int *year, *month, *day; + int *hour, *minute, *second, *millisec; + char *z; +}; + +/*----------------------------------------------------------------- + * Enhanced access methods + */ +struct toml_datum_t { + int ok; + union { + toml_timestamp_t *ts; /* ts must be freed after use */ + char *s; /* string value. s must be freed after use */ + int b; /* bool value */ + int64_t i; /* int value */ + double d; /* double value */ + } u; +}; + +/* on arrays: */ +/* ... retrieve size of array. */ +TOML_EXTERN int toml_array_nelem(const toml_array_t *arr); +/* ... retrieve values using index. */ +TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx); +/* ... retrieve array or table using index. */ +TOML_EXTERN toml_array_t *toml_array_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_table_t *toml_table_at(const toml_array_t *arr, int idx); + +/* on tables: */ +/* ... retrieve the key in table at keyidx. Return 0 if out of range. */ +TOML_EXTERN const char *toml_key_in(const toml_table_t *tab, int keyidx); +/* ... returns 1 if key exists in tab, 0 otherwise */ +TOML_EXTERN int toml_key_exists(const toml_table_t *tab, const char *key); +/* ... retrieve values using key. */ +TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t *arr, + const char *key); +TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key); +TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t *arr, const char *key); +TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t *arr, + const char *key); +TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t *arr, + const char *key); +/* .. retrieve array or table using key. */ +TOML_EXTERN toml_array_t *toml_array_in(const toml_table_t *tab, + const char *key); +TOML_EXTERN toml_table_t *toml_table_in(const toml_table_t *tab, + const char *key); + +/*----------------------------------------------------------------- + * lesser used + */ +/* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */ +TOML_EXTERN char toml_array_kind(const toml_array_t *arr); + +/* For array kind 'v'alue, return the type of values + i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed + 0 if unknown +*/ +TOML_EXTERN char toml_array_type(const toml_array_t *arr); + +/* Return the key of an array */ +TOML_EXTERN const char *toml_array_key(const toml_array_t *arr); + +/* Return the number of key-values in a table */ +TOML_EXTERN int toml_table_nkval(const toml_table_t *tab); + +/* Return the number of arrays in a table */ +TOML_EXTERN int toml_table_narr(const toml_table_t *tab); + +/* Return the number of sub-tables in a table */ +TOML_EXTERN int toml_table_ntab(const toml_table_t *tab); + +/* Return the key of a table*/ +TOML_EXTERN const char *toml_table_key(const toml_table_t *tab); + +/*-------------------------------------------------------------- + * misc + */ +TOML_EXTERN int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret); +TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); +TOML_EXTERN void toml_set_memutil(void *(*xxmalloc)(size_t), + void (*xxfree)(void *)); + +/*-------------------------------------------------------------- + * deprecated + */ +/* A raw value, must be processed by toml_rto* before using. */ +typedef const char *toml_raw_t; +TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key); +TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t *arr, int idx); +TOML_EXTERN int toml_rtos(toml_raw_t s, char **ret); +TOML_EXTERN int toml_rtob(toml_raw_t s, int *ret); +TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t *ret); +TOML_EXTERN int toml_rtod(toml_raw_t s, double *ret); +TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double *ret, char *buf, int buflen); +TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t *ret); + +#endif /* TOML_H */ diff --git a/toml_cat.c b/toml_cat.c new file mode 100644 index 0000000..6fd5ec1 --- /dev/null +++ b/toml_cat.c @@ -0,0 +1,322 @@ +/* + MIT License + + Copyright (c) 2017 CK Tan + https://github.com/cktan/tomlc99 + + 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. +*/ + +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "toml.h" +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct node_t node_t; +struct node_t { + const char *key; + toml_table_t *tab; +}; + +node_t stack[20]; +int stacktop = 0; +int indent = 0; + +static void prindent() { + for (int i = 0; i < indent; i++) + printf(" "); +} + +static void print_string(const char *s) { + int ok = 1; + for (const char *p = s; *p && ok; p++) { + int ch = *p; + ok = isprint(ch) && ch != '"' && ch != '\\'; + } + + if (ok) { + printf("\"%s\"", s); + return; + } + + int len = strlen(s); + + printf("\""); + for (; len; len--, s++) { + int ch = *s; + if (isprint(ch) && ch != '"' && ch != '\\') { + putchar(ch); + continue; + } + + switch (ch) { + case 0x8: + printf("\\b"); + continue; + case 0x9: + printf("\\t"); + continue; + case 0xa: + printf("\\n"); + continue; + case 0xc: + printf("\\f"); + continue; + case 0xd: + printf("\\r"); + continue; + case '"': + printf("\\\""); + continue; + case '\\': + printf("\\\\"); + continue; + default: + printf("\\0x%02x", ch & 0xff); + continue; + } + } + printf("\""); +} + +static void print_array(toml_array_t *arr); + +static void print_timestamp(toml_datum_t d) { + if (d.u.ts->year) { + printf("%04d-%02d-%02d%s", *d.u.ts->year, *d.u.ts->month, *d.u.ts->day, + d.u.ts->hour ? "T" : ""); + } + if (d.u.ts->hour) { + printf("%02d:%02d:%02d", *d.u.ts->hour, *d.u.ts->minute, *d.u.ts->second); + if (d.u.ts->millisec) { + printf(".%03d", *d.u.ts->millisec); + } + if (d.u.ts->z) { + printf("%s", d.u.ts->z); + } + } +} + +static void print_table(toml_table_t *curtab) { + toml_datum_t d; + int i; + const char *key; + toml_array_t *arr; + toml_table_t *tab; + + for (i = 0; 0 != (key = toml_key_in(curtab, i)); i++) { + + if (0 != (arr = toml_array_in(curtab, key))) { + prindent(); + printf("%s = [\n", key); + indent++; + print_array(arr); + indent--; + prindent(); + printf("],\n"); + continue; + } + + if (0 != (tab = toml_table_in(curtab, key))) { + stack[stacktop].key = key; + stack[stacktop].tab = tab; + stacktop++; + prindent(); + printf("%s = {\n", key); + indent++; + print_table(tab); + indent--; + prindent(); + printf("},\n"); + stacktop--; + continue; + } + + d = toml_string_in(curtab, key); + if (d.ok) { + prindent(); + printf("%s = ", key); + print_string(d.u.s); + printf(",\n"); + free(d.u.s); + continue; + } + + d = toml_bool_in(curtab, key); + if (d.ok) { + prindent(); + printf("%s = %s,\n", key, d.u.b ? "true" : "false"); + continue; + } + + d = toml_int_in(curtab, key); + if (d.ok) { + prindent(); + printf("%s = %" PRId64 ",\n", key, d.u.i); + continue; + } + + d = toml_double_in(curtab, key); + if (d.ok) { + prindent(); + printf("%s = %f,\n", key, d.u.d); + continue; + } + + d = toml_timestamp_in(curtab, key); + if (d.ok) { + prindent(); + printf("%s = ", key); + print_timestamp(d); + printf(",\n"); + free(d.u.ts); + continue; + } + + fflush(stdout); + fprintf(stderr, "ERROR: unable to decode value in table\n"); + exit(1); + } +} + +static void print_array(toml_array_t *curarr) { + toml_datum_t d; + toml_array_t *arr; + toml_table_t *tab; + const int n = toml_array_nelem(curarr); + + for (int i = 0; i < n; i++) { + + if (0 != (arr = toml_array_at(curarr, i))) { + prindent(); + printf("[\n"); + indent++; + print_array(arr); + indent--; + prindent(); + printf("],\n"); + continue; + } + + if (0 != (tab = toml_table_at(curarr, i))) { + prindent(); + printf("{\n"); + indent++; + print_table(tab); + indent--; + prindent(); + printf("},\n"); + continue; + } + + d = toml_string_at(curarr, i); + if (d.ok) { + prindent(); + print_string(d.u.s); + printf(",\n"); + free(d.u.s); + continue; + } + + d = toml_bool_at(curarr, i); + if (d.ok) { + prindent(); + printf("%s,\n", d.u.b ? "true" : "false"); + continue; + } + + d = toml_int_at(curarr, i); + if (d.ok) { + prindent(); + printf("%" PRId64 ",\n", d.u.i); + continue; + } + + d = toml_double_at(curarr, i); + if (d.ok) { + prindent(); + printf("%f,\n", d.u.d); + continue; + } + + d = toml_timestamp_at(curarr, i); + if (d.ok) { + prindent(); + print_timestamp(d); + printf(",\n"); + free(d.u.ts); + continue; + } + + fflush(stdout); + fprintf(stderr, "ERROR: unable to decode value in array\n"); + exit(1); + } +} + +static void cat(FILE *fp) { + char errbuf[200]; + + toml_table_t *tab = toml_parse_file(fp, errbuf, sizeof(errbuf)); + if (!tab) { + fprintf(stderr, "ERROR: %s\n", errbuf); + return; + } + + stack[stacktop].tab = tab; + stack[stacktop].key = ""; + stacktop++; + printf("{\n"); + indent++; + print_table(tab); + indent--; + printf("}\n"); + stacktop--; + + toml_free(tab); +} + +int main(int argc, const char *argv[]) { + int i; + if (argc == 1) { + cat(stdin); + } else { + for (i = 1; i < argc; i++) { + + FILE *fp = fopen(argv[i], "r"); + if (!fp) { + fprintf(stderr, "ERROR: cannot open %s: %s\n", argv[i], + strerror(errno)); + exit(1); + } + cat(fp); + fclose(fp); + } + } + return 0; +} diff --git a/toml_json.c b/toml_json.c new file mode 100644 index 0000000..a939a23 --- /dev/null +++ b/toml_json.c @@ -0,0 +1,217 @@ +/* + MIT License + + Copyright (c) 2017 CK Tan + https://github.com/cktan/tomlc99 + + 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. +*/ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "toml.h" +#include +#include +#include +#include +#include + +static void print_escape_string(const char *s) { + for (; *s; s++) { + int ch = *s; + switch (ch) { + case '\b': + printf("\\b"); + break; + case '\t': + printf("\\t"); + break; + case '\n': + printf("\\n"); + break; + case '\f': + printf("\\f"); + break; + case '\r': + printf("\\r"); + break; + case '"': + printf("\\\""); + break; + case '\\': + printf("\\\\"); + break; + default: + printf("%c", ch); + break; + } + } +} + +static void print_raw(const char *s) { + char *sval; + int64_t ival; + int bval; + double dval; + toml_timestamp_t ts; + char dbuf[100]; + + if (0 == toml_rtos(s, &sval)) { + printf("{\"type\":\"string\",\"value\":\""); + print_escape_string(sval); + printf("\"}"); + free(sval); + } else if (0 == toml_rtoi(s, &ival)) { + printf("{\"type\":\"integer\",\"value\":\"%" PRId64 "\"}", ival); + } else if (0 == toml_rtob(s, &bval)) { + printf("{\"type\":\"bool\",\"value\":\"%s\"}", bval ? "true" : "false"); + } else if (0 == toml_rtod_ex(s, &dval, dbuf, sizeof(dbuf))) { + printf("{\"type\":\"float\",\"value\":\"%s\"}", dbuf); + } else if (0 == toml_rtots(s, &ts)) { + char millisec[10]; + if (ts.millisec) + sprintf(millisec, ".%03d", *ts.millisec); + else + millisec[0] = 0; + if (ts.year && ts.hour) { + printf("{\"type\":\"datetime\",\"value\":\"%04d-%02d-%02dT%02d:%02d:%02d%" + "s%s\"}", + *ts.year, *ts.month, *ts.day, *ts.hour, *ts.minute, *ts.second, + millisec, (ts.z ? ts.z : "")); + } else if (ts.year) { + printf("{\"type\":\"date\",\"value\":\"%04d-%02d-%02d\"}", *ts.year, + *ts.month, *ts.day); + } else if (ts.hour) { + printf("{\"type\":\"time\",\"value\":\"%02d:%02d:%02d%s\"}", *ts.hour, + *ts.minute, *ts.second, millisec); + } + } else { + fprintf(stderr, "unknown type\n"); + exit(1); + } +} + +static void print_array(toml_array_t *arr); +static void print_table(toml_table_t *curtab) { + int i; + const char *key; + const char *raw; + toml_array_t *arr; + toml_table_t *tab; + + printf("{"); + for (i = 0; 0 != (key = toml_key_in(curtab, i)); i++) { + + printf("%s\"", i > 0 ? "," : ""); + print_escape_string(key); + printf("\":"); + + if (0 != (raw = toml_raw_in(curtab, key))) { + print_raw(raw); + } else if (0 != (arr = toml_array_in(curtab, key))) { + print_array(arr); + } else if (0 != (tab = toml_table_in(curtab, key))) { + print_table(tab); + } else { + abort(); + } + } + printf("}"); +} + +static void print_table_array(toml_array_t *curarr) { + int i; + toml_table_t *tab; + + printf("["); + for (i = 0; 0 != (tab = toml_table_at(curarr, i)); i++) { + printf("%s", i > 0 ? "," : ""); + print_table(tab); + } + printf("]"); +} + +static void print_array(toml_array_t *curarr) { + toml_array_t *arr; + const char *raw; + int i; + + if (toml_array_kind(curarr) == 't') { + print_table_array(curarr); + return; + } + + printf("{\"type\":\"array\",\"value\":["); + switch (toml_array_kind(curarr)) { + + case 'v': + for (i = 0; 0 != (raw = toml_raw_at(curarr, i)); i++) { + printf("%s", i > 0 ? "," : ""); + print_raw(raw); + } + break; + + case 'a': + for (i = 0; 0 != (arr = toml_array_at(curarr, i)); i++) { + printf("%s", i > 0 ? "," : ""); + print_array(arr); + } + break; + + default: + break; + } + printf("]}"); +} + +static void cat(FILE *fp) { + char errbuf[200]; + + toml_table_t *tab = toml_parse_file(fp, errbuf, sizeof(errbuf)); + if (!tab) { + fprintf(stderr, "ERROR: %s\n", errbuf); + exit(1); + } + + print_table(tab); + printf("\n"); + + toml_free(tab); +} + +int main(int argc, const char *argv[]) { + int i; + if (argc == 1) { + cat(stdin); + } else { + for (i = 1; i < argc; i++) { + + FILE *fp = fopen(argv[i], "r"); + if (!fp) { + fprintf(stderr, "ERROR: cannot open %s: %s\n", argv[i], + strerror(errno)); + exit(1); + } + cat(fp); + fclose(fp); + } + } + return 0; +} diff --git a/toml_sample.c b/toml_sample.c new file mode 100644 index 0000000..a8c3b9d --- /dev/null +++ b/toml_sample.c @@ -0,0 +1,60 @@ +#include "toml.h" +#include +#include +#include +#include + +static void fatal(const char *msg, const char *msg1) { + fprintf(stderr, "ERROR: %s%s\n", msg, msg1 ? msg1 : ""); + exit(1); +} + +int main() { + FILE *fp; + char errbuf[200]; + + // 1. Read and parse toml file + fp = fopen("sample.toml", "r"); + if (!fp) { + fatal("cannot open sample.toml - ", strerror(errno)); + } + + toml_table_t *conf = toml_parse_file(fp, errbuf, sizeof(errbuf)); + fclose(fp); + + if (!conf) { + fatal("cannot parse - ", errbuf); + } + + // 2. Traverse to a table. + toml_table_t *server = toml_table_in(conf, "server"); + if (!server) { + fatal("missing [server]", ""); + } + + // 3. Extract values + toml_datum_t host = toml_string_in(server, "host"); + if (!host.ok) { + fatal("cannot read server.host", ""); + } + + toml_array_t *portarray = toml_array_in(server, "port"); + if (!portarray) { + fatal("cannot read server.port", ""); + } + + printf("host: %s\n", host.u.s); + printf("port: "); + for (int i = 0;; i++) { + toml_datum_t port = toml_int_at(portarray, i); + if (!port.ok) + break; + printf("%d ", (int)port.u.i); + } + printf("\n"); + + // 4. Free memory + free(host.u.s); + toml_free(conf); + return 0; +} diff --git a/unittest/Makefile b/unittest/Makefile new file mode 100644 index 0000000..0fb102f --- /dev/null +++ b/unittest/Makefile @@ -0,0 +1,11 @@ +CFLAGS = -g -I.. + +TESTS = t1 + +all: $(TESTS) + +t1: t1.c ../toml.c + +clean: + rm -f $(TESTS) + diff --git a/unittest/t1.c b/unittest/t1.c new file mode 100644 index 0000000..3da3901 --- /dev/null +++ b/unittest/t1.c @@ -0,0 +1,73 @@ +#include "../toml.h" +#include +#include +#include +#include + +int main(int argc, const char *argv[]) { + char xxbuf[6], buf[6]; + int64_t xxcode, code; + int xxsize; + + xxsize = 2, xxcode = 0x80; + memcpy(xxbuf, "\xc2\x80", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 2, xxcode = 0x7ff; + memcpy(xxbuf, "\xdf\xbf", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 3, xxcode = 0x800; + memcpy(xxbuf, "\xe0\xa0\x80", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 3, xxcode = 0xfffd; + memcpy(xxbuf, "\xef\xbf\xbd", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 4, xxcode = 0x10000; + memcpy(xxbuf, "\xf0\x90\x80\x80", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 4, xxcode = 0x1fffff; + memcpy(xxbuf, "\xf7\xbf\xbf\xbf", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 5, xxcode = 0x200000; + memcpy(xxbuf, "\xf8\x88\x80\x80\x80", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 5, xxcode = 0x3ffffff; + memcpy(xxbuf, "\xfb\xbf\xbf\xbf\xbf", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 6, xxcode = 0x4000000; + memcpy(xxbuf, "\xfc\x84\x80\x80\x80\x80", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + xxsize = 6, xxcode = 0x7fffffff; + memcpy(xxbuf, "\xfd\xbf\xbf\xbf\xbf\xbf", xxsize); + assert(toml_ucs_to_utf8(xxcode, buf) == xxsize && + 0 == memcmp(buf, xxbuf, xxsize)); + assert(toml_utf8_to_ucs(buf, xxsize, &code) == xxsize && code == xxcode); + + return 0; +}