aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Makefile79
-rw-r--r--README.md300
-rw-r--r--doc/api.md263
-rw-r--r--include/ucl.h1045
-rw-r--r--src/ucl_chartable.h267
-rw-r--r--src/ucl_emitter.c829
-rw-r--r--src/ucl_hash.c120
-rw-r--r--src/ucl_hash.h91
-rw-r--r--src/ucl_internal.h292
-rw-r--r--src/ucl_parser.c1872
-rw-r--r--src/ucl_util.c1157
-rw-r--r--src/xxhash.c475
-rw-r--r--src/xxhash.h164
-rw-r--r--tests/1.in14
-rw-r--r--tests/1.res13
-rw-r--r--tests/2.in24
-rw-r--r--tests/2.res37
-rw-r--r--tests/3.in31
-rw-r--r--tests/3.res25
-rw-r--r--tests/4.in47
-rw-r--r--tests/4.res36
-rw-r--r--tests/5.in1
-rw-r--r--tests/5.res1
-rw-r--r--tests/6.in4
-rw-r--r--tests/6.res2
-rw-r--r--tests/7.in0
-rw-r--r--tests/7.res1
-rw-r--r--tests/8.in21
-rw-r--r--tests/8.res36
-rw-r--r--tests/9-comment.inc1
-rw-r--r--tests/9-empty.inc0
-rw-r--r--tests/9.in13
-rw-r--r--tests/9.inc1
-rw-r--r--tests/9.res5
-rw-r--r--tests/generate.res20
-rwxr-xr-xtests/run_tests.sh58
-rw-r--r--tests/test_basic.c151
-rw-r--r--tests/test_generate.c126
-rw-r--r--tests/test_speed.c149
-rw-r--r--uthash/uthash.h720
-rw-r--r--uthash/utlist.h757
-rw-r--r--uthash/utstring.h410
-rw-r--r--utils/chargen.c128
-rw-r--r--utils/objdump.c159
45 files changed, 9948 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000000..ea72388b31f0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.cproject
+.project
+.settings
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000000..82c7057166d2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,79 @@
+CC ?= gcc
+DESTDIR ?= /usr/local
+LD ?= gcc
+C_COMMON_FLAGS ?= -fPIC -Wall -W -Wno-unused-parameter -Wno-pointer-sign -I./include -I./uthash -I./src
+MAJOR_VERSION = 0
+MINOR_VERSION = 2
+PATCH_VERSION = 8
+VERSION = "$(MAJOR_VERSION).$(MINOR_VERSION).$(PATCH_VERSION)"
+SONAME = libucl.so
+SONAME_FULL = $(SONAME).$(MAJOR_VERSION)
+OBJDIR ?= .obj
+TESTDIR ?= tests
+SRCDIR ?= src
+INCLUDEDIR ?= include
+MKDIR ?= mkdir
+INSTALL ?= install
+RM ?= rm
+RMDIR ?= rmdir
+LN ?= ln
+LD_SHARED_FLAGS ?= -Wl,-soname,$(SONAME) -shared -lm
+LD_UCL_FLAGS ?= -L$(OBJDIR) -Wl,-rpath,$(OBJDIR) -lucl
+LD_ADD ?= -lrt
+COPT_FLAGS ?= -g -O0
+HDEPS = $(SRCDIR)/ucl_hash.h $(SRCDIR)/ucl_chartable.h $(SRCDIR)/ucl_internal.h $(INCLUDEDIR)/ucl.h $(SRCDIR)/xxhash.h
+OBJECTS = $(OBJDIR)/ucl_hash.o $(OBJDIR)/ucl_util.o $(OBJDIR)/ucl_parser.o $(OBJDIR)/ucl_emitter.o $(OBJDIR)/xxhash.o
+
+all: $(OBJDIR) $(OBJDIR)/$(SONAME)
+
+$(OBJDIR)/$(SONAME): $(OBJDIR)/$(SONAME_FULL)
+ $(LN) -sf $(SONAME_FULL) $(OBJDIR)/$(SONAME)
+
+$(OBJDIR)/$(SONAME_FULL): $(OBJECTS)
+ $(CC) -o $(OBJDIR)/$(SONAME_FULL) $(OBJECTS) $(LD_SHARED_FLAGS) $(LDFLAGS) $(SSL_LIBS) $(FETCH_LIBS)
+
+$(OBJDIR):
+ @$(MKDIR) -p $(OBJDIR)
+
+# Compile rules
+$(OBJDIR)/ucl_util.o: $(SRCDIR)/ucl_util.c $(HDEPS)
+ $(CC) -o $(OBJDIR)/ucl_util.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_util.c
+$(OBJDIR)/ucl_parser.o: $(SRCDIR)/ucl_parser.c $(HDEPS)
+ $(CC) -o $(OBJDIR)/ucl_parser.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_parser.c
+$(OBJDIR)/ucl_emitter.o: $(SRCDIR)/ucl_emitter.c $(HDEPS)
+ $(CC) -o $(OBJDIR)/ucl_emitter.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_emitter.c
+$(OBJDIR)/ucl_hash.o: $(SRCDIR)/ucl_hash.c $(HDEPS)
+ $(CC) -o $(OBJDIR)/ucl_hash.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_hash.c
+$(OBJDIR)/xxhash.o: $(SRCDIR)/xxhash.c $(HDEPS)
+ $(CC) -o $(OBJDIR)/xxhash.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/xxhash.c
+
+clean:
+ $(RM) $(OBJDIR)/*.o $(OBJDIR)/$(SONAME_FULL) $(OBJDIR)/$(SONAME) $(OBJDIR)/chargen $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(OBJDIR)/objdump $(OBJDIR)/test_generate
+ $(RMDIR) $(OBJDIR)
+
+# Utils
+
+chargen: utils/chargen.c $(OBJDIR)/$(SONAME)
+ $(CC) -o $(OBJDIR)/chargen $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) $(LDFLAGS) utils/chargen.c
+objdump: utils/objdump.c $(OBJDIR)/$(SONAME)
+ $(CC) -o $(OBJDIR)/objdump $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) $(LDFLAGS) utils/objdump.c $(LD_UCL_FLAGS)
+
+# Tests
+
+test: $(OBJDIR) $(OBJDIR)/$(SONAME) $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(OBJDIR)/test_generate
+
+run-test: test
+ TEST_DIR=$(TESTDIR) $(TESTDIR)/run_tests.sh $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(OBJDIR)/test_generate
+
+$(OBJDIR)/test_basic: $(TESTDIR)/test_basic.c $(OBJDIR)/$(SONAME)
+ $(CC) -o $(OBJDIR)/test_basic $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) $(LDFLAGS) $(TESTDIR)/test_basic.c $(LD_UCL_FLAGS)
+$(OBJDIR)/test_speed: $(TESTDIR)/test_speed.c $(OBJDIR)/$(SONAME)
+ $(CC) -o $(OBJDIR)/test_speed $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) $(LDFLAGS) $(TESTDIR)/test_speed.c $(LD_UCL_FLAGS) $(LD_ADD)
+$(OBJDIR)/test_generate: $(TESTDIR)/test_generate.c $(OBJDIR)/$(SONAME)
+ $(CC) -o $(OBJDIR)/test_generate $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) $(LDFLAGS) $(TESTDIR)/test_generate.c $(LD_UCL_FLAGS) $(LD_ADD)
+
+install: $(OBJDIR)/$(SONAME)
+ $(INSTALL) -m0755 $(SONAME) $(DESTDIR)/lib/$(SONAME)
+ $(INSTALL) -m0644 include/ucl.h $(DESTDIR)/include/ucl.h
+
+.PHONY: clean $(OBJDIR)
diff --git a/README.md b/README.md
new file mode 100644
index 000000000000..b5a373a5d2dc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,300 @@
+## Introduction
+
+This document describes the main features and principles of the configuration
+language called `UCL` - universal configuration language.
+
+If you are looking for the libucl API documentation you can find it at [this page](doc/api.md).
+
+## Basic structure
+
+UCL is heavily infused by `nginx` configuration as the example of a convenient configuration
+system. However, UCL is fully compatible with `JSON` format and is able to parse json files.
+For example, you can write the same configuration in the following ways:
+
+* in nginx like:
+
+```nginx
+param = value;
+section {
+ param = value;
+ param1 = value1;
+ flag = true;
+ number = 10k;
+ time = 0.2s;
+ string = "something";
+ subsection {
+ host = {
+ host = "hostname";
+ port = 900;
+ }
+ host = {
+ host = "hostname";
+ port = 901;
+ }
+ }
+}
+```
+
+* or in JSON:
+
+```json
+{
+ "param": "value",
+ "param1": "value1",
+ "flag": true,
+ "subsection": {
+ "host": [
+ {
+ "host": "hostname",
+ "port": 900
+ },
+ {
+ "host": "hostname",
+ "port": 901
+ }
+ ]
+ }
+}
+```
+
+## Improvements to the json notation.
+
+There are various things that make ucl configuration more convenient for editing than strict json:
+
+### General syntax sugar
+
+* Braces are not necessary to enclose a top object: it is automatically treated as an object:
+
+```json
+"key": "value"
+```
+is equal to:
+```json
+{"key": "value"}
+```
+
+* There is no requirement of quotes for strings and keys, moreover, `:` may be replaced `=` or even be skipped for objects:
+
+```nginx
+key = value;
+section {
+ key = value;
+}
+```
+is equal to:
+```json
+{
+ "key": "value",
+ "section": {
+ "key": "value"
+ }
+}
+```
+
+* No commas mess: you can safely place a comma or semicolon for the last element in an array or an object:
+
+```json
+{
+ "key1": "value",
+ "key2": "value",
+}
+```
+### Automatic arrays creation
+
+* Non-unique keys in an object are allowed and are automatically converted to the arrays internally:
+
+```json
+{
+ "key": "value1",
+ "key": "value2"
+}
+```
+is converted to:
+```json
+{
+ "key": ["value1", "value2"]
+}
+```
+
+### Named keys hierarchy
+
+UCL accepts named keys and organize them into objects hierarchy internally. Here is an example of this process:
+```nginx
+section "blah" {
+ key = value;
+}
+section foo {
+ key = value;
+}
+```
+
+is converted to the following object:
+
+```nginx
+section {
+ blah {
+ key = value;
+ }
+ foo {
+ key = value;
+ }
+}
+```
+
+Plain definitions may be more complex and contain more than a single level of nested objects:
+
+```nginx
+section "blah" "foo" {
+ key = value;
+}
+```
+
+is presented as:
+
+```nginx
+section {
+ blah {
+ foo {
+ key = value;
+ }
+ }
+}
+```
+
+### Convenient numbers and booleans
+
+* Numbers can have suffixes to specify standard multipliers:
+ + `[kKmMgG]` - standard 10 base multipliers (so `1k` is translated to 1000)
+ + `[kKmMgG]b` - 2 power multipliers (so `1kb` is translated to 1024)
+ + `[s|min|d|w|y]` - time multipliers, all time values are translated to float number of seconds, for example `10min` is translated to 600.0 and `10ms` is translated to 0.01
+* Hexadecimal integers can be used by `0x` prefix, for example `key = 0xff`. However, floating point values can use decimal base only.
+* Booleans can be specified as `true` or `yes` or `on` and `false` or `no` or `off`.
+* It is still possible to treat numbers and booleans as strings by enclosing them in double quotes.
+
+## General improvements
+
+### Commments
+
+UCL supports different style of comments:
+
+* single line: `#`
+* multiline: `/* ... */`
+
+Multiline comments may be nested:
+```c
+# Sample single line comment
+/*
+ some comment
+ /* nested comment */
+ end of comment
+*/
+```
+
+### Macros support
+
+UCL supports external macros both multiline and single line ones:
+```nginx
+.macro "sometext";
+.macro {
+ Some long text
+ ....
+};
+```
+There are two internal macros provided by UCL:
+
+* `include` - read a file `/path/to/file` or an url `http://example.com/file` and include it to the current place of
+UCL configuration;
+* `try\_include` - try to read a file or url and include it but do not create a fatal error if a file or url is not accessible;
+* `includes` - read a file or an url like the previous macro, but fetch and check the signature file (which is obtained
+by `.sig` suffix appending).
+
+Public keys which are used for the last command are specified by the concrete UCL user.
+
+### Variables support
+
+UCL supports variables in input. Variables are registered by a user of the UCL parser and can be presented in the following forms:
+
+* `${VARIABLE}`
+* `$VARIABLE`
+
+UCL currently does not support nested variables. To escape variables one could use double dollar signs:
+
+* `$${VARIABLE}` is converted to `${VARIABLE}`
+* `$$VARIABLE` is converted to `$VARIABLE`
+
+However, if no valid variables are found in a string, no expansion will be performed (and `$$` thus remains unchanged). This may be a subject
+to change in future libucl releases.
+
+### Multiline strings
+
+UCL can handle multiline strings as well as single line ones. It uses shell/perl like notation for such objects:
+```
+key = <<EOD
+some text
+splitted to
+lines
+EOD
+```
+
+In this example `key` will be interpreted as the following string: `some text\nsplitted to\nlines`.
+Here are some rules for this syntax:
+
+* Multiline terminator must start just after `<<` symbols and it must consist of capital letters only (e.g. `<<eof` or `<< EOF` won't work);
+* Terminator must end with a single newline character (and no spaces are allowed between terminator and newline character);
+* To finish multiline string you need to include a terminator string just after newline and followed by a newline (no spaces or other characters are allowed as well);
+* The initial and the final newlines are not inserted to the resulting string, but you can still specify newlines at the begin and at the end of a value, for example:
+
+```
+key <<EOD
+
+some
+text
+
+EOD
+```
+
+## Emitter
+
+Each UCL object can be serialized to one of the three supported formats:
+
+* `JSON` - canonic json notation (with spaces indented structure);
+* `Compacted JSON` - compact json notation (without spaces or newlines);
+* `Configuration` - nginx like notation;
+* `YAML` - yaml inlined notation.
+
+## Performance
+
+Are UCL parser and emitter fast enough? Well, there are some numbers.
+I got a 19Mb file that consist of ~700 thousands lines of json (obtained via
+http://www.json-generator.com/). Then I checked jansson library that performs json
+parsing and emitting and compared it with UCL. Here are results:
+
+```
+jansson: parsed json in 1.3899 seconds
+jansson: emitted object in 0.2609 seconds
+
+ucl: parsed input in 0.6649 seconds
+ucl: emitted config in 0.2423 seconds
+ucl: emitted json in 0.2329 seconds
+ucl: emitted compact json in 0.1811 seconds
+ucl: emitted yaml in 0.2489 seconds
+```
+
+So far, UCL seems to be significantly faster than jansson on parsing and slightly faster on emitting. Moreover,
+UCL compiled with optimizations (-O3) performs significantly faster:
+```
+ucl: parsed input in 0.3002 seconds
+ucl: emitted config in 0.1174 seconds
+ucl: emitted json in 0.1174 seconds
+ucl: emitted compact json in 0.0991 seconds
+ucl: emitted yaml in 0.1354 seconds
+```
+
+You can do your own benchmarks by running `make test` in libucl top directory.
+
+## Conclusion
+
+UCL has clear design that should be very convenient for reading and writing. At the same time it is compatible with
+JSON language and therefore can be used as a simple JSON parser. Macroes logic provides an ability to extend configuration
+language (for example by including some lua code) and comments allows to disable or enable the parts of a configuration
+quickly.
diff --git a/doc/api.md b/doc/api.md
new file mode 100644
index 000000000000..a85f8513a040
--- /dev/null
+++ b/doc/api.md
@@ -0,0 +1,263 @@
+Synopsis
+========
+
+`#include <ucl.h>`
+
+Description
+===========
+
+Libucl is a parser and `C` API to parse and generate `ucl` objects. Libucl consist of several groups of functions:
+
+### Parser functions
+Used to parse `ucl` files and provide interface to extract `ucl` object
+
+### Emitting functions
+Convert `ucl` objects to some textual or binary representation.
+
+### Conversion functions
+Help to convert `ucl` objects to C types
+
+### Generation functions
+Allow creating of `ucl` objects from C types
+
+### Iteration functions
+Iterate over `ucl` objects
+
+### Utility functions
+Provide basic utilities to manage `ucl` objects
+
+# Parser functions
+
+Parser functions operates with `struct ucl_parser`.
+
+### ucl_parser_new
+
+~~~C
+struct ucl_parser* ucl_parser_new (int flags);
+~~~
+
+Creates new parser with the specified flags:
+
+- `UCL_PARSER_KEY_LOWERCASE` - lowercase keys parsed
+- `UCL_PARSER_ZEROCOPY` - try to use zero-copy mode when reading files (in zero-copy mode text chunk being parsed without copying strings so it should exist till any object parsed is used)
+
+### ucl_parser_register_macro
+
+~~~C
+void ucl_parser_register_macro (struct ucl_parser *parser,
+ const char *macro, ucl_macro_handler handler, void* ud);
+~~~
+
+Register new macro with name .`macro` parsed by handler `handler` that accepts opaque data pointer `ud`. Macro handler should be of the following type:
+
+~~~C
+bool (*ucl_macro_handler) (const unsigned char *data,
+ size_t len, void* ud);`
+~~~
+
+Handler function accepts macro text `data` of length `len` and the opaque pointer `ud`. If macro is parsed successfully the handler should return `true`. `false` indicates parsing failure and the parser can be terminated.
+
+### ucl_parser_register_variable
+
+~~~C
+void ucl_parser_register_variable (struct ucl_parser *parser,
+ const char *var, const char *value);
+~~~
+
+Register new variable $`var` that should be replaced by the parser to the `value` string.
+
+### ucl_parser_add_chunk
+
+~~~C
+bool ucl_parser_add_chunk (struct ucl_parser *parser,
+ const unsigned char *data, size_t len);
+~~~
+
+Add new text chunk with `data` of length `len` to the parser. At the moment, `libucl` parser is not a streamlined parser and chunk *must* contain the *valid* ucl object. For example, this object should be valid:
+
+~~~json
+{ "var": "value" }
+~~~
+
+while this one won't be parsed correctly:
+
+~~~json
+{ "var":
+~~~
+
+This limitation may possible be removed in future.
+
+### ucl_parser_add_file
+
+~~~C
+bool ucl_parser_add_file (struct ucl_parser *parser,
+ const char *filename);
+~~~
+
+Load file `filename` and parse it with the specified `parser`. This function uses `mmap` call to load file, therefore, it should not be `shrinked` during parsing. Otherwise, `libucl` can cause memory corruption and terminate the calling application. This function is also used by the internal handler of `include` macro, hence, this macro has the same limitation.
+
+### ucl_parser_get_object
+
+~~~C
+ucl_object_t* ucl_parser_get_object (struct ucl_parser *parser);
+~~~
+
+If the `ucl` data has been parsed correctly this function returns the top object for the parser. Otherwise, this function returns the `NULL` pointer. The reference count for `ucl` object returned is increased by one, therefore, a caller should decrease reference by using `ucl_object_unref` to free object after usage.
+
+### ucl_parser_get_error
+
+~~~C
+const char *ucl_parser_get_error(struct ucl_parser *parser);
+~~~
+
+Returns the constant error string for the parser object. If no error occurred during parsing a `NULL` object is returned. A caller should not try to free or modify this string.
+
+### ucl_parser_free
+
+~~~C
+void ucl_parser_free (struct ucl_parser *parser);
+~~~
+
+Frees memory occupied by the parser object. The reference count for top object is decreased as well, however if the function `ucl_parser_get_object` was called previously then the top object won't be freed.
+
+### ucl_pubkey_add
+
+~~~C
+bool ucl_pubkey_add (struct ucl_parser *parser,
+ const unsigned char *key, size_t len);
+~~~
+
+This function adds a public key from text blob `key` of length `len` to the `parser` object. This public key should be in the `PEM` format and can be used by `.includes` macro for checking signatures of files included. `Openssl` support should be enabled to make this function working. If a key cannot be added (e.g. due to format error) or `openssl` was not linked to `libucl` then this function returns `false`.
+
+### ucl_parser_set_filevars
+
+~~~C
+bool ucl_parser_set_filevars (struct ucl_parser *parser,
+ const char *filename, bool need_expand);
+~~~
+
+Add the standard file variables to the `parser` based on the `filename` specified:
+
+- `$FILENAME` - a filename of `ucl` input
+- `$CURDIR` - a current directory of the input
+
+For example, if a `filename` param is `../something.conf` then the variables will have the following values:
+
+- `$FILENAME` - "../something.conf"
+- `$CURDIR` - ".."
+
+if `need_expand` parameter is `true` then all relative paths are expanded using `realpath` call. In this example if `..` is `/etc/dir` then variables will have these values:
+
+- `$FILENAME` - "/etc/something.conf"
+- `$CURDIR` - "/etc"
+
+## Parser usage example
+
+The following example loads, parses and extracts `ucl` object from stdin using `libucl` parser functions (the length of input is limited to 8K):
+
+~~~C
+char inbuf[8192];
+struct ucl_parser *parser = NULL;
+int ret = 0, r = 0;
+ucl_object_t *obj = NULL;
+FILE *in;
+
+in = stdin;
+parser = ucl_parser_new (0);
+while (!feof (in) && r < (int)sizeof (inbuf)) {
+ r += fread (inbuf + r, 1, sizeof (inbuf) - r, in);
+}
+ucl_parser_add_chunk (parser, inbuf, r);
+fclose (in);
+
+if (ucl_parser_get_error (parser)) {
+ printf ("Error occured: %s\n", ucl_parser_get_error (parser));
+ ret = 1;
+}
+else {
+ obj = ucl_parser_get_object (parser);
+}
+
+if (parser != NULL) {
+ ucl_parser_free (parser);
+}
+if (obj != NULL) {
+ ucl_object_unref (obj);
+}
+return ret;
+~~~
+
+# Emitting functions
+
+Libucl can transform UCL objects to a number of tectual formats:
+
+- configuration (`UCL_EMIT_CONFIG`) - nginx like human readable configuration file where implicit arrays are transformed to the duplicate keys
+- compact json: `UCL_EMIT_JSON_COMPACT` - single line valid json without spaces
+- formatted json: `UCL_EMIT_JSON` - pretty formatted JSON with newlines and spaces
+- compact yaml: `UCL_EMIT_YAML` - compact YAML output
+
+Moreover, libucl API allows to select a custom set of emitting functions allowing
+efficent and zero-copy output of libucl objects. Libucl uses the following structure to support this feature:
+
+~~~C
+struct ucl_emitter_functions {
+ /** Append a single character */
+ int (*ucl_emitter_append_character) (unsigned char c, size_t nchars, void *ud);
+ /** Append a string of a specified length */
+ int (*ucl_emitter_append_len) (unsigned const char *str, size_t len, void *ud);
+ /** Append a 64 bit integer */
+ int (*ucl_emitter_append_int) (int64_t elt, void *ud);
+ /** Append floating point element */
+ int (*ucl_emitter_append_double) (double elt, void *ud);
+ /** Opaque userdata pointer */
+ void *ud;
+};
+~~~
+
+This structure defines the following callbacks:
+
+- `ucl_emitter_append_character` - a function that is called to append `nchars` characters equal to `c`
+- `ucl_emitter_append_len` - used to append a string of length `len` starting from pointer `str`
+- `ucl_emitter_append_int` - this function applies to integer numbers
+- `ucl_emitter_append_double` - this function is intended to output floating point variable
+
+The set of these functions could be used to output text formats of `UCL` objects to different structures or streams.
+
+Libucl provides the following functions for emitting UCL objects:
+
+### ucl_object_emit
+
+~~~C
+unsigned char *ucl_object_emit (ucl_object_t *obj, enum ucl_emitter emit_type);
+~~~
+
+Allocate a string that is suitable to fit the underlying UCL object `obj` and fill it with the textual representation of the object `obj` according to style `emit_type`. The caller should free the returned string after using.
+
+### ucl_object_emit_full
+
+~~~C
+bool ucl_object_emit_full (ucl_object_t *obj, enum ucl_emitter emit_type,
+ struct ucl_emitter_functions *emitter);
+~~~
+
+This function is similar to the previous with the exception that it accepts the additional argument `emitter` that defines the concrete set of output functions. This emit function could be useful for custom structures or streams emitters (including C++ ones, for example).
+
+# Conversion functions
+
+Conversion functions are used to convert UCL objects to primitive types, such as strings, numbers or boolean values. There are two types of conversion functions:
+
+- safe: try to convert an ucl object to a primitive type and fail if such a conversion is not possible
+- unsafe: return primitive type without additional checks, if the object cannot be converted then some reasonable default is returned (NULL for strings and 0 for numbers)
+
+Also there is a single `ucl_object_tostring_forced` function that converts any UCL object (including compound types - arrays and objects) to a string representation. For compound and numeric types this function performs emitting to a compact json format actually.
+
+Here is a list of all conversion functions:
+
+- `ucl_object_toint` - returns `int64_t` of UCL object
+- `ucl_object_todouble` - returns `double` of UCL object
+- `ucl_object_toboolean` - returns `bool` of UCL object
+- `ucl_object_tostring` - returns `const char *` of UCL object (this string is NULL terminated)
+- `ucl_object_tolstring` - returns `const char *` and `size_t` len of UCL object (string can be not NULL terminated)
+- `ucl_object_tostring_forced` - returns string representation of any UCL object
+
+Strings returned by these pointers are associated with the UCL object and exist over its lifetime. A caller should not free this memory. \ No newline at end of file
diff --git a/include/ucl.h b/include/ucl.h
new file mode 100644
index 000000000000..8376b8adc462
--- /dev/null
+++ b/include/ucl.h
@@ -0,0 +1,1045 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UCL_H_
+#define UCL_H_
+
+#include <string.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+/**
+ * @mainpage
+ * This is a reference manual for UCL API. You may find the description of UCL format by following this
+ * [github repository](https://github.com/vstakhov/libucl).
+ *
+ * This manual has several main sections:
+ * - @ref structures
+ * - @ref utils
+ * - @ref parser
+ * - @ref emitter
+ */
+
+/**
+ * @file ucl.h
+ * @brief UCL parsing and emitting functions
+ *
+ * UCL is universal configuration language, which is a form of
+ * JSON with less strict rules that make it more comfortable for
+ * using as a configuration language
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Memory allocation utilities
+ * UCL_ALLOC(size) - allocate memory for UCL
+ * UCL_FREE(size, ptr) - free memory of specified size at ptr
+ * Default: malloc and free
+ */
+#ifndef UCL_ALLOC
+#define UCL_ALLOC(size) malloc(size)
+#endif
+#ifndef UCL_FREE
+#define UCL_FREE(size, ptr) free(ptr)
+#endif
+
+#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#define UCL_WARN_UNUSED_RESULT \
+ __attribute__((warn_unused_result))
+#else
+#define UCL_WARN_UNUSED_RESULT
+#endif
+
+/**
+ * @defgroup structures Structures and types
+ * UCL defines several enumeration types used for error reporting or specifying flags and attributes.
+ *
+ * @{
+ */
+
+/**
+ * The common error codes returned by ucl parser
+ */
+typedef enum ucl_error {
+ UCL_EOK = 0, /**< No error */
+ UCL_ESYNTAX, /**< Syntax error occurred during parsing */
+ UCL_EIO, /**< IO error occurred during parsing */
+ UCL_ESTATE, /**< Invalid state machine state */
+ UCL_ENESTED, /**< Input has too many recursion levels */
+ UCL_EMACRO, /**< Error processing a macro */
+ UCL_EINTERNAL, /**< Internal unclassified error */
+ UCL_ESSL /**< SSL error */
+} ucl_error_t;
+
+/**
+ * #ucl_object_t may have one of specified types, some types are compatible with each other and some are not.
+ * For example, you can always convert #UCL_TIME to #UCL_FLOAT. Also you can convert #UCL_FLOAT to #UCL_INTEGER
+ * by loosing floating point. Every object may be converted to a string by #ucl_object_tostring_forced() function.
+ *
+ */
+typedef enum ucl_type {
+ UCL_OBJECT = 0, /**< UCL object - key/value pairs */
+ UCL_ARRAY, /**< UCL array */
+ UCL_INT, /**< Integer number */
+ UCL_FLOAT, /**< Floating point number */
+ UCL_STRING, /**< Null terminated string */
+ UCL_BOOLEAN, /**< Boolean value */
+ UCL_TIME, /**< Time value (floating point number of seconds) */
+ UCL_USERDATA, /**< Opaque userdata pointer (may be used in macros) */
+ UCL_NULL /**< Null value */
+} ucl_type_t;
+
+/**
+ * You can use one of these types to serialise #ucl_object_t by using ucl_object_emit().
+ */
+typedef enum ucl_emitter {
+ UCL_EMIT_JSON = 0, /**< Emit fine formatted JSON */
+ UCL_EMIT_JSON_COMPACT, /**< Emit compacted JSON */
+ UCL_EMIT_CONFIG, /**< Emit human readable config format */
+ UCL_EMIT_YAML /**< Emit embedded YAML format */
+} ucl_emitter_t;
+
+/**
+ * These flags defines parser behaviour. If you specify #UCL_PARSER_ZEROCOPY you must ensure
+ * that the input memory is not freed if an object is in use. Moreover, if you want to use
+ * zero-terminated keys and string values then you should not use zero-copy mode, as in this case
+ * UCL still has to perform copying implicitly.
+ */
+typedef enum ucl_parser_flags {
+ UCL_PARSER_KEY_LOWERCASE = 0x1, /**< Convert all keys to lower case */
+ UCL_PARSER_ZEROCOPY = 0x2 /**< Parse input in zero-copy mode if possible */
+} ucl_parser_flags_t;
+
+/**
+ * String conversion flags, that are used in #ucl_object_fromstring_common function.
+ */
+typedef enum ucl_string_flags {
+ UCL_STRING_ESCAPE = 0x1, /**< Perform JSON escape */
+ UCL_STRING_TRIM = 0x2, /**< Trim leading and trailing whitespaces */
+ UCL_STRING_PARSE_BOOLEAN = 0x4, /**< Parse passed string and detect boolean */
+ UCL_STRING_PARSE_INT = 0x8, /**< Parse passed string and detect integer number */
+ UCL_STRING_PARSE_DOUBLE = 0x10, /**< Parse passed string and detect integer or float number */
+ UCL_STRING_PARSE_NUMBER = UCL_STRING_PARSE_INT|UCL_STRING_PARSE_DOUBLE , /**<
+ Parse passed string and detect number */
+ UCL_STRING_PARSE = UCL_STRING_PARSE_BOOLEAN|UCL_STRING_PARSE_NUMBER, /**<
+ Parse passed string (and detect booleans and numbers) */
+ UCL_STRING_PARSE_BYTES = 0x20 /**< Treat numbers as bytes */
+} ucl_string_flags_t;
+
+/**
+ * Basic flags for an object
+ */
+typedef enum ucl_object_flags {
+ UCL_OBJECT_ALLOCATED_KEY = 1, /**< An object has key allocated internally */
+ UCL_OBJECT_ALLOCATED_VALUE = 2, /**< An object has a string value allocated internally */
+ UCL_OBJECT_NEED_KEY_ESCAPE = 4 /**< The key of an object need to be escaped on output */
+} ucl_object_flags_t;
+
+/**
+ * UCL object structure. Please mention that the most of fields should not be touched by
+ * UCL users. In future, this structure may be converted to private one.
+ */
+typedef struct ucl_object_s {
+ /**
+ * Variant value type
+ */
+ union {
+ int64_t iv; /**< Int value of an object */
+ const char *sv; /**< String value of an object */
+ double dv; /**< Double value of an object */
+ struct ucl_object_s *av; /**< Array */
+ void *ov; /**< Object */
+ void* ud; /**< Opaque user data */
+ } value;
+ const char *key; /**< Key of an object */
+ struct ucl_object_s *next; /**< Array handle */
+ struct ucl_object_s *prev; /**< Array handle */
+ unsigned char* trash_stack[2]; /**< Pointer to allocated chunks */
+ unsigned keylen; /**< Lenght of a key */
+ unsigned len; /**< Size of an object */
+ enum ucl_type type; /**< Real type */
+ uint16_t ref; /**< Reference count */
+ uint16_t flags; /**< Object flags */
+} ucl_object_t;
+
+/** @} */
+
+/**
+ * @defgroup utils Utility functions
+ * A number of utility functions simplify handling of UCL objects
+ *
+ * @{
+ */
+/**
+ * Copy and return a key of an object, returned key is zero-terminated
+ * @param obj CL object
+ * @return zero terminated key
+ */
+char* ucl_copy_key_trash (ucl_object_t *obj);
+
+/**
+ * Copy and return a string value of an object, returned key is zero-terminated
+ * @param obj CL object
+ * @return zero terminated string representation of object value
+ */
+char* ucl_copy_value_trash (ucl_object_t *obj);
+
+/**
+ * Creates a new object
+ * @return new object
+ */
+static inline ucl_object_t* ucl_object_new (void) UCL_WARN_UNUSED_RESULT;
+static inline ucl_object_t *
+ucl_object_new (void)
+{
+ ucl_object_t *new;
+ new = malloc (sizeof (ucl_object_t));
+ if (new != NULL) {
+ memset (new, 0, sizeof (ucl_object_t));
+ new->ref = 1;
+ new->type = UCL_NULL;
+ }
+ return new;
+}
+
+/**
+ * Create new object with type specified
+ * @param type type of a new object
+ * @return new object
+ */
+static inline ucl_object_t* ucl_object_typed_new (unsigned int type) UCL_WARN_UNUSED_RESULT;
+static inline ucl_object_t *
+ucl_object_typed_new (unsigned int type)
+{
+ ucl_object_t *new;
+ new = malloc (sizeof (ucl_object_t));
+ if (new != NULL) {
+ memset (new, 0, sizeof (ucl_object_t));
+ new->ref = 1;
+ new->type = (type <= UCL_NULL ? type : UCL_NULL);
+ }
+ return new;
+}
+
+/**
+ * Convert any string to an ucl object making the specified transformations
+ * @param str fixed size or NULL terminated string
+ * @param len length (if len is zero, than str is treated as NULL terminated)
+ * @param flags conversion flags
+ * @return new object
+ */
+ucl_object_t * ucl_object_fromstring_common (const char *str, size_t len,
+ enum ucl_string_flags flags) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create a UCL object from the specified string
+ * @param str NULL terminated string, will be json escaped
+ * @return new object
+ */
+static inline ucl_object_t *
+ucl_object_fromstring (const char *str)
+{
+ return ucl_object_fromstring_common (str, 0, UCL_STRING_ESCAPE);
+}
+
+/**
+ * Create a UCL object from the specified string
+ * @param str fixed size string, will be json escaped
+ * @param len length of a string
+ * @return new object
+ */
+static inline ucl_object_t *
+ucl_object_fromlstring (const char *str, size_t len)
+{
+ return ucl_object_fromstring_common (str, len, UCL_STRING_ESCAPE);
+}
+
+/**
+ * Create an object from an integer number
+ * @param iv number
+ * @return new object
+ */
+static inline ucl_object_t *
+ucl_object_fromint (int64_t iv)
+{
+ ucl_object_t *obj;
+
+ obj = ucl_object_new ();
+ if (obj != NULL) {
+ obj->type = UCL_INT;
+ obj->value.iv = iv;
+ }
+
+ return obj;
+}
+
+/**
+ * Create an object from a float number
+ * @param dv number
+ * @return new object
+ */
+static inline ucl_object_t *
+ucl_object_fromdouble (double dv)
+{
+ ucl_object_t *obj;
+
+ obj = ucl_object_new ();
+ if (obj != NULL) {
+ obj->type = UCL_FLOAT;
+ obj->value.dv = dv;
+ }
+
+ return obj;
+}
+
+/**
+ * Create an object from a boolean
+ * @param bv bool value
+ * @return new object
+ */
+static inline ucl_object_t *
+ucl_object_frombool (bool bv)
+{
+ ucl_object_t *obj;
+
+ obj = ucl_object_new ();
+ if (obj != NULL) {
+ obj->type = UCL_BOOLEAN;
+ obj->value.iv = bv;
+ }
+
+ return obj;
+}
+
+/**
+ * Insert a object 'elt' to the hash 'top' and associate it with key 'key'
+ * @param top destination object (will be created automatically if top is NULL)
+ * @param elt element to insert (must NOT be NULL)
+ * @param key key to associate with this object (either const or preallocated)
+ * @param keylen length of the key (or 0 for NULL terminated keys)
+ * @param copy_key make an internal copy of key
+ * @return new value of top object
+ */
+ucl_object_t* ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Replace a object 'elt' to the hash 'top' and associate it with key 'key', old object will be unrefed,
+ * if no object has been found this function works like ucl_object_insert_key()
+ * @param top destination object (will be created automatically if top is NULL)
+ * @param elt element to insert (must NOT be NULL)
+ * @param key key to associate with this object (either const or preallocated)
+ * @param keylen length of the key (or 0 for NULL terminated keys)
+ * @param copy_key make an internal copy of key
+ * @return new value of top object
+ */
+ucl_object_t* ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Insert a object 'elt' to the hash 'top' and associate it with key 'key', if the specified key exist,
+ * try to merge its content
+ * @param top destination object (will be created automatically if top is NULL)
+ * @param elt element to insert (must NOT be NULL)
+ * @param key key to associate with this object (either const or preallocated)
+ * @param keylen length of the key (or 0 for NULL terminated keys)
+ * @param copy_key make an internal copy of key
+ * @return new value of top object
+ */
+ucl_object_t* ucl_object_insert_key_merged (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Append an element to the front of array object
+ * @param top destination object (will be created automatically if top is NULL)
+ * @param elt element to append (must NOT be NULL)
+ * @return new value of top object
+ */
+static inline ucl_object_t * ucl_array_append (ucl_object_t *top,
+ ucl_object_t *elt) UCL_WARN_UNUSED_RESULT;
+static inline ucl_object_t *
+ucl_array_append (ucl_object_t *top, ucl_object_t *elt)
+{
+ ucl_object_t *head;
+
+ if (elt == NULL) {
+ return NULL;
+ }
+
+ if (top == NULL) {
+ top = ucl_object_typed_new (UCL_ARRAY);
+ top->value.av = elt;
+ elt->next = NULL;
+ elt->prev = elt;
+ top->len = 1;
+ }
+ else {
+ head = top->value.av;
+ if (head == NULL) {
+ top->value.av = elt;
+ elt->prev = elt;
+ }
+ else {
+ elt->prev = head->prev;
+ head->prev->next = elt;
+ head->prev = elt;
+ }
+ elt->next = NULL;
+ top->len ++;
+ }
+
+ return top;
+}
+
+/**
+ * Append an element to the start of array object
+ * @param top destination object (will be created automatically if top is NULL)
+ * @param elt element to append (must NOT be NULL)
+ * @return new value of top object
+ */
+static inline ucl_object_t * ucl_array_prepend (ucl_object_t *top,
+ ucl_object_t *elt) UCL_WARN_UNUSED_RESULT;
+static inline ucl_object_t *
+ucl_array_prepend (ucl_object_t *top, ucl_object_t *elt)
+{
+ ucl_object_t *head;
+
+ if (elt == NULL) {
+ return NULL;
+ }
+
+ if (top == NULL) {
+ top = ucl_object_typed_new (UCL_ARRAY);
+ top->value.av = elt;
+ elt->next = NULL;
+ elt->prev = elt;
+ top->len = 1;
+ }
+ else {
+ head = top->value.av;
+ if (head == NULL) {
+ top->value.av = elt;
+ elt->prev = elt;
+ }
+ else {
+ elt->prev = head->prev;
+ head->prev = elt;
+ }
+ elt->next = head;
+ top->value.av = elt;
+ top->len ++;
+ }
+
+ return top;
+}
+
+/**
+ * Removes an element `elt` from the array `top`. Caller must unref the returned object when it is not
+ * needed.
+ * @param top array ucl object
+ * @param elt element to remove
+ * @return removed element or NULL if `top` is NULL or not an array
+ */
+static inline ucl_object_t *
+ucl_array_delete (ucl_object_t *top, ucl_object_t *elt)
+{
+ ucl_object_t *head;
+
+ if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) {
+ return NULL;
+ }
+ head = top->value.av;
+
+ if (elt->prev == elt) {
+ top->value.av = NULL;
+ }
+ else if (elt == head) {
+ elt->next->prev = elt->prev;
+ top->value.av = elt->next;
+ }
+ else {
+ elt->prev->next = elt->next;
+ if (elt->next) {
+ elt->next->prev = elt->prev;
+ }
+ else {
+ head->prev = elt->prev;
+ }
+ }
+ elt->next = NULL;
+ elt->prev = elt;
+ top->len --;
+
+ return elt;
+}
+
+/**
+ * Returns the first element of the array `top`
+ * @param top array ucl object
+ * @return element or NULL if `top` is NULL or not an array
+ */
+static inline ucl_object_t *
+ucl_array_head (ucl_object_t *top)
+{
+ if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) {
+ return NULL;
+ }
+ return top->value.av;
+}
+
+/**
+ * Returns the last element of the array `top`
+ * @param top array ucl object
+ * @return element or NULL if `top` is NULL or not an array
+ */
+static inline ucl_object_t *
+ucl_array_tail (ucl_object_t *top)
+{
+ if (top == NULL || top->type != UCL_ARRAY || top->value.av == NULL) {
+ return NULL;
+ }
+ return top->value.av->prev;
+}
+
+/**
+ * Removes the last element from the array `top`. Caller must unref the returned object when it is not
+ * needed.
+ * @param top array ucl object
+ * @return removed element or NULL if `top` is NULL or not an array
+ */
+static inline ucl_object_t *
+ucl_array_pop_last (ucl_object_t *top)
+{
+ return ucl_array_delete (top, ucl_array_tail (top));
+}
+
+/**
+ * Removes the first element from the array `top`. Caller must unref the returned object when it is not
+ * needed.
+ * @param top array ucl object
+ * @return removed element or NULL if `top` is NULL or not an array
+ */
+static inline ucl_object_t *
+ucl_array_pop_first (ucl_object_t *top)
+{
+ return ucl_array_delete (top, ucl_array_head (top));
+}
+
+/**
+ * Append a element to another element forming an implicit array
+ * @param head head to append (may be NULL)
+ * @param elt new element
+ * @return new head if applicable
+ */
+static inline ucl_object_t * ucl_elt_append (ucl_object_t *head,
+ ucl_object_t *elt) UCL_WARN_UNUSED_RESULT;
+static inline ucl_object_t *
+ucl_elt_append (ucl_object_t *head, ucl_object_t *elt)
+{
+
+ if (head == NULL) {
+ elt->next = NULL;
+ elt->prev = elt;
+ head = elt;
+ }
+ else {
+ elt->prev = head->prev;
+ head->prev->next = elt;
+ head->prev = elt;
+ elt->next = NULL;
+ }
+
+ return head;
+}
+
+/**
+ * Converts an object to double value
+ * @param obj CL object
+ * @param target target double variable
+ * @return true if conversion was successful
+ */
+static inline bool
+ucl_object_todouble_safe (ucl_object_t *obj, double *target)
+{
+ if (obj == NULL) {
+ return false;
+ }
+ switch (obj->type) {
+ case UCL_INT:
+ *target = obj->value.iv; /* Probaly could cause overflow */
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ *target = obj->value.dv;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Unsafe version of \ref ucl_obj_todouble_safe
+ * @param obj CL object
+ * @return double value
+ */
+static inline double
+ucl_object_todouble (ucl_object_t *obj)
+{
+ double result = 0.;
+
+ ucl_object_todouble_safe (obj, &result);
+ return result;
+}
+
+/**
+ * Converts an object to integer value
+ * @param obj CL object
+ * @param target target integer variable
+ * @return true if conversion was successful
+ */
+static inline bool
+ucl_object_toint_safe (ucl_object_t *obj, int64_t *target)
+{
+ if (obj == NULL) {
+ return false;
+ }
+ switch (obj->type) {
+ case UCL_INT:
+ *target = obj->value.iv;
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ *target = obj->value.dv; /* Loosing of decimal points */
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Unsafe version of \ref ucl_obj_toint_safe
+ * @param obj CL object
+ * @return int value
+ */
+static inline int64_t
+ucl_object_toint (ucl_object_t *obj)
+{
+ int64_t result = 0;
+
+ ucl_object_toint_safe (obj, &result);
+ return result;
+}
+
+/**
+ * Converts an object to boolean value
+ * @param obj CL object
+ * @param target target boolean variable
+ * @return true if conversion was successful
+ */
+static inline bool
+ucl_object_toboolean_safe (ucl_object_t *obj, bool *target)
+{
+ if (obj == NULL) {
+ return false;
+ }
+ switch (obj->type) {
+ case UCL_BOOLEAN:
+ *target = (obj->value.iv == true);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Unsafe version of \ref ucl_obj_toboolean_safe
+ * @param obj CL object
+ * @return boolean value
+ */
+static inline bool
+ucl_object_toboolean (ucl_object_t *obj)
+{
+ bool result = false;
+
+ ucl_object_toboolean_safe (obj, &result);
+ return result;
+}
+
+/**
+ * Converts an object to string value
+ * @param obj CL object
+ * @param target target string variable, no need to free value
+ * @return true if conversion was successful
+ */
+static inline bool
+ucl_object_tostring_safe (ucl_object_t *obj, const char **target)
+{
+ if (obj == NULL) {
+ return false;
+ }
+
+ switch (obj->type) {
+ case UCL_STRING:
+ *target = ucl_copy_value_trash (obj);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Unsafe version of \ref ucl_obj_tostring_safe
+ * @param obj CL object
+ * @return string value
+ */
+static inline const char *
+ucl_object_tostring (ucl_object_t *obj)
+{
+ const char *result = NULL;
+
+ ucl_object_tostring_safe (obj, &result);
+ return result;
+}
+
+/**
+ * Convert any object to a string in JSON notation if needed
+ * @param obj CL object
+ * @return string value
+ */
+static inline const char *
+ucl_object_tostring_forced (ucl_object_t *obj)
+{
+ return ucl_copy_value_trash (obj);
+}
+
+/**
+ * Return string as char * and len, string may be not zero terminated, more efficient that \ref ucl_obj_tostring as it
+ * allows zero-copy (if #UCL_PARSER_ZEROCOPY has been used during parsing)
+ * @param obj CL object
+ * @param target target string variable, no need to free value
+ * @param tlen target length
+ * @return true if conversion was successful
+ */
+static inline bool
+ucl_object_tolstring_safe (ucl_object_t *obj, const char **target, size_t *tlen)
+{
+ if (obj == NULL) {
+ return false;
+ }
+ switch (obj->type) {
+ case UCL_STRING:
+ *target = obj->value.sv;
+ *tlen = obj->len;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Unsafe version of \ref ucl_obj_tolstring_safe
+ * @param obj CL object
+ * @return string value
+ */
+static inline const char *
+ucl_object_tolstring (ucl_object_t *obj, size_t *tlen)
+{
+ const char *result = NULL;
+
+ ucl_object_tolstring_safe (obj, &result, tlen);
+ return result;
+}
+
+/**
+ * Return object identified by a key in the specified object
+ * @param obj object to get a key from (must be of type UCL_OBJECT)
+ * @param key key to search
+ * @return object matched the specified key or NULL if key is not found
+ */
+ucl_object_t * ucl_object_find_key (ucl_object_t *obj, const char *key);
+
+/**
+ * Return object identified by a fixed size key in the specified object
+ * @param obj object to get a key from (must be of type UCL_OBJECT)
+ * @param key key to search
+ * @param klen length of a key
+ * @return object matched the specified key or NULL if key is not found
+ */
+ucl_object_t *ucl_object_find_keyl (ucl_object_t *obj, const char *key, size_t klen);
+
+/**
+ * Returns a key of an object as a NULL terminated string
+ * @param obj CL object
+ * @return key or NULL if there is no key
+ */
+static inline const char *
+ucl_object_key (ucl_object_t *obj)
+{
+ return ucl_copy_key_trash (obj);
+}
+
+/**
+ * Returns a key of an object as a fixed size string (may be more efficient)
+ * @param obj CL object
+ * @param len target key length
+ * @return key pointer
+ */
+static inline const char *
+ucl_object_keyl (ucl_object_t *obj, size_t *len)
+{
+ *len = obj->keylen;
+ return obj->key;
+}
+
+/**
+ * Free ucl object
+ * @param obj ucl object to free
+ */
+void ucl_object_free (ucl_object_t *obj);
+
+/**
+ * Increase reference count for an object
+ * @param obj object to ref
+ */
+static inline ucl_object_t *
+ucl_object_ref (ucl_object_t *obj) {
+ obj->ref ++;
+ return obj;
+}
+
+/**
+ * Decrease reference count for an object
+ * @param obj object to unref
+ */
+static inline void
+ucl_object_unref (ucl_object_t *obj) {
+ if (obj != NULL && --obj->ref <= 0) {
+ ucl_object_free (obj);
+ }
+}
+/**
+ * Opaque iterator object
+ */
+typedef void* ucl_object_iter_t;
+
+/**
+ * Get next key from an object
+ * @param obj object to iterate
+ * @param iter opaque iterator, must be set to NULL on the first call:
+ * ucl_object_iter_t it = NULL;
+ * while ((cur = ucl_iterate_object (obj, &it)) != NULL) ...
+ * @return the next object or NULL
+ */
+ucl_object_t* ucl_iterate_object (ucl_object_t *obj, ucl_object_iter_t *iter, bool expand_values);
+/** @} */
+
+
+/**
+ * @defgroup parser Parsing functions
+ * These functions are used to parse UCL objects
+ *
+ * @{
+ */
+
+/**
+ * Macro handler for a parser
+ * @param data the content of macro
+ * @param len the length of content
+ * @param ud opaque user data
+ * @param err error pointer
+ * @return true if macro has been parsed
+ */
+typedef bool (*ucl_macro_handler) (const unsigned char *data, size_t len, void* ud);
+
+/* Opaque parser */
+struct ucl_parser;
+
+/**
+ * Creates new parser object
+ * @param pool pool to allocate memory from
+ * @return new parser object
+ */
+struct ucl_parser* ucl_parser_new (int flags);
+
+/**
+ * Register new handler for a macro
+ * @param parser parser object
+ * @param macro macro name (without leading dot)
+ * @param handler handler (it is called immediately after macro is parsed)
+ * @param ud opaque user data for a handler
+ */
+void ucl_parser_register_macro (struct ucl_parser *parser, const char *macro,
+ ucl_macro_handler handler, void* ud);
+
+/**
+ * Register new parser variable
+ * @param parser parser object
+ * @param var variable name
+ * @param value variable value
+ */
+void ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
+ const char *value);
+
+/**
+ * Load new chunk to a parser
+ * @param parser parser structure
+ * @param data the pointer to the beginning of a chunk
+ * @param len the length of a chunk
+ * @param err if *err is NULL it is set to parser error
+ * @return true if chunk has been added and false in case of error
+ */
+bool ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data, size_t len);
+
+/**
+ * Load and add data from a file
+ * @param parser parser structure
+ * @param filename the name of file
+ * @param err if *err is NULL it is set to parser error
+ * @return true if chunk has been added and false in case of error
+ */
+bool ucl_parser_add_file (struct ucl_parser *parser, const char *filename);
+
+/**
+ * Get a top object for a parser
+ * @param parser parser structure
+ * @param err if *err is NULL it is set to parser error
+ * @return top parser object or NULL
+ */
+ucl_object_t* ucl_parser_get_object (struct ucl_parser *parser);
+
+/**
+ * Get the error string if failing
+ * @param parser parser object
+ */
+const char *ucl_parser_get_error(struct ucl_parser *parser);
+/**
+ * Free ucl parser object
+ * @param parser parser object
+ */
+void ucl_parser_free (struct ucl_parser *parser);
+
+/**
+ * Add new public key to parser for signatures check
+ * @param parser parser object
+ * @param key PEM representation of a key
+ * @param len length of the key
+ * @param err if *err is NULL it is set to parser error
+ * @return true if a key has been successfully added
+ */
+bool ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len);
+
+/**
+ * Set FILENAME and CURDIR variables in parser
+ * @param parser parser object
+ * @param filename filename to set or NULL to set FILENAME to "undef" and CURDIR to getcwd()
+ * @param need_expand perform realpath() if this variable is true and filename is not NULL
+ * @return true if variables has been set
+ */
+bool ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename,
+ bool need_expand);
+
+/** @} */
+
+/**
+ * @defgroup emitter Emitting functions
+ * These functions are used to serialise UCL objects to some string representation.
+ *
+ * @{
+ */
+
+/**
+ * Structure using for emitter callbacks
+ */
+struct ucl_emitter_functions {
+ /** Append a single character */
+ int (*ucl_emitter_append_character) (unsigned char c, size_t nchars, void *ud);
+ /** Append a string of a specified length */
+ int (*ucl_emitter_append_len) (unsigned const char *str, size_t len, void *ud);
+ /** Append a 64 bit integer */
+ int (*ucl_emitter_append_int) (int64_t elt, void *ud);
+ /** Append floating point element */
+ int (*ucl_emitter_append_double) (double elt, void *ud);
+ /** Opaque userdata pointer */
+ void *ud;
+};
+
+/**
+ * Emit object to a string
+ * @param obj object
+ * @param emit_type if type is #UCL_EMIT_JSON then emit json, if type is
+ * #UCL_EMIT_CONFIG then emit config like object
+ * @return dump of an object (must be freed after using) or NULL in case of error
+ */
+unsigned char *ucl_object_emit (ucl_object_t *obj, enum ucl_emitter emit_type);
+
+/**
+ * Emit object to a string
+ * @param obj object
+ * @param emit_type if type is #UCL_EMIT_JSON then emit json, if type is
+ * #UCL_EMIT_CONFIG then emit config like object
+ * @return dump of an object (must be freed after using) or NULL in case of error
+ */
+bool ucl_object_emit_full (ucl_object_t *obj, enum ucl_emitter emit_type,
+ struct ucl_emitter_functions *emitter);
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+/*
+ * XXX: Poorly named API functions, need to replace them with the appropriate
+ * named function. All API functions *must* use naming ucl_object_*. Usage of
+ * ucl_obj* should be avoided.
+ */
+#define ucl_obj_todouble_safe ucl_object_todouble_safe
+#define ucl_obj_todouble ucl_object_todouble
+#define ucl_obj_tostring ucl_object_tostring
+#define ucl_obj_tostring_safe ucl_object_tostring_safe
+#define ucl_obj_tolstring ucl_object_tolstring
+#define ucl_obj_tolstring_safe ucl_object_tolstring_safe
+#define ucl_obj_toint ucl_object_toint
+#define ucl_obj_toint_safe ucl_object_toint_safe
+#define ucl_obj_toboolean ucl_object_toboolean
+#define ucl_obj_toboolean_safe ucl_object_toboolean_safe
+#define ucl_obj_get_key ucl_object_find_key
+#define ucl_obj_get_keyl ucl_object_find_keyl
+#define ucl_obj_unref ucl_object_unref
+#define ucl_obj_ref ucl_object_ref
+#define ucl_obj_free ucl_object_free
+
+#endif /* UCL_H_ */
diff --git a/src/ucl_chartable.h b/src/ucl_chartable.h
new file mode 100644
index 000000000000..5248e117c63d
--- /dev/null
+++ b/src/ucl_chartable.h
@@ -0,0 +1,267 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UCL_CHARTABLE_H_
+#define UCL_CHARTABLE_H_
+
+#include "ucl_internal.h"
+
+static const unsigned int ucl_chartable[255] = {
+UCL_CHARACTER_VALUE_END, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE,
+UCL_CHARACTER_WHITESPACE|UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_KEY_SEP|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_END|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_END|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE,
+UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_WHITESPACE|UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_KEY_SEP|UCL_CHARACTER_UCL_UNSAFE /* */,
+UCL_CHARACTER_VALUE_STR /* ! */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE /* " */,
+UCL_CHARACTER_VALUE_END /* # */, UCL_CHARACTER_VALUE_STR /* $ */,
+UCL_CHARACTER_VALUE_STR /* % */, UCL_CHARACTER_VALUE_STR /* & */,
+UCL_CHARACTER_VALUE_STR /* ' */, UCL_CHARACTER_VALUE_STR /* ( */,
+UCL_CHARACTER_VALUE_STR /* ) */, UCL_CHARACTER_VALUE_STR /* * */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* + */,
+UCL_CHARACTER_VALUE_END /* , */,
+UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* - */,
+UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* . */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE /* / */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 0 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 1 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 2 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 3 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 4 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 5 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 6 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 7 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 8 */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT_START|UCL_CHARACTER_VALUE_DIGIT /* 9 */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_KEY_SEP|UCL_CHARACTER_UCL_UNSAFE /* : */,
+UCL_CHARACTER_VALUE_END /* ; */, UCL_CHARACTER_VALUE_STR /* < */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_KEY_SEP|UCL_CHARACTER_UCL_UNSAFE /* = */,
+UCL_CHARACTER_VALUE_STR /* > */, UCL_CHARACTER_VALUE_STR /* ? */,
+UCL_CHARACTER_VALUE_STR /* @ */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* A */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* B */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* C */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* D */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* E */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* F */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* G */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* H */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* I */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* J */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* K */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* L */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* M */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* N */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* O */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* P */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* Q */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* R */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* S */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* T */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* U */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* V */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* W */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* X */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* Y */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* Z */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_UCL_UNSAFE /* [ */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE|UCL_CHARACTER_JSON_UNSAFE|UCL_CHARACTER_UCL_UNSAFE /* \ */,
+UCL_CHARACTER_VALUE_END /* ] */, UCL_CHARACTER_VALUE_STR /* ^ */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR /* _ */,
+UCL_CHARACTER_VALUE_STR /* ` */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* a */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* b */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* c */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* d */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* e */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* f */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* g */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* h */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* i */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* j */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* k */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* l */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* m */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* n */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* o */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* p */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* q */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* r */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* s */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* t */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT|UCL_CHARACTER_ESCAPE /* u */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* v */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* w */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* x */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* y */,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_VALUE_DIGIT /* z */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_UCL_UNSAFE /* { */,
+UCL_CHARACTER_VALUE_STR /* | */, UCL_CHARACTER_VALUE_END /* } */,
+UCL_CHARACTER_VALUE_STR /* ~ */, UCL_CHARACTER_DENIED,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR,
+UCL_CHARACTER_KEY_START|UCL_CHARACTER_KEY|UCL_CHARACTER_VALUE_STR
+};
+
+static inline bool
+ucl_test_character (unsigned char c, int type_flags)
+{
+ return (ucl_chartable[c] & type_flags) != 0;
+}
+
+#endif /* UCL_CHARTABLE_H_ */
diff --git a/src/ucl_emitter.c b/src/ucl_emitter.c
new file mode 100644
index 000000000000..51bb09aad7cc
--- /dev/null
+++ b/src/ucl_emitter.c
@@ -0,0 +1,829 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <float.h>
+#include <math.h>
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "ucl_chartable.h"
+
+/**
+ * @file rcl_emitter.c
+ * Serialise UCL object to various of output formats
+ */
+
+
+static void ucl_obj_write_json (ucl_object_t *obj,
+ struct ucl_emitter_functions *func,
+ unsigned int tabs,
+ bool start_tabs,
+ bool compact);
+static void ucl_elt_write_json (ucl_object_t *obj,
+ struct ucl_emitter_functions *func,
+ unsigned int tabs,
+ bool start_tabs,
+ bool compact);
+static void ucl_elt_write_config (ucl_object_t *obj,
+ struct ucl_emitter_functions *func,
+ unsigned int tabs,
+ bool start_tabs,
+ bool is_top,
+ bool expand_array);
+static void ucl_elt_write_yaml (ucl_object_t *obj,
+ struct ucl_emitter_functions *func,
+ unsigned int tabs,
+ bool start_tabs,
+ bool compact,
+ bool expand_array);
+static void ucl_elt_array_write_yaml (ucl_object_t *obj,
+ struct ucl_emitter_functions *func,
+ unsigned int tabs,
+ bool start_tabs,
+ bool is_top);
+
+/**
+ * Add tabulation to the output buffer
+ * @param buf target buffer
+ * @param tabs number of tabs to add
+ */
+static inline void
+ucl_add_tabs (struct ucl_emitter_functions *func, unsigned int tabs, bool compact)
+{
+ if (!compact) {
+ func->ucl_emitter_append_character (' ', tabs * 4, func->ud);
+ }
+}
+
+/**
+ * Serialise string
+ * @param str string to emit
+ * @param buf target buffer
+ */
+static void
+ucl_elt_string_write_json (const char *str, size_t size,
+ struct ucl_emitter_functions *func)
+{
+ const char *p = str, *c = str;
+ size_t len = 0;
+
+ func->ucl_emitter_append_character ('"', 1, func->ud);
+ while (size) {
+ if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
+ if (len > 0) {
+ func->ucl_emitter_append_len (c, len, func->ud);
+ }
+ switch (*p) {
+ case '\n':
+ func->ucl_emitter_append_len ("\\n", 2, func->ud);
+ break;
+ case '\r':
+ func->ucl_emitter_append_len ("\\r", 2, func->ud);
+ break;
+ case '\b':
+ func->ucl_emitter_append_len ("\\b", 2, func->ud);
+ break;
+ case '\t':
+ func->ucl_emitter_append_len ("\\t", 2, func->ud);
+ break;
+ case '\f':
+ func->ucl_emitter_append_len ("\\f", 2, func->ud);
+ break;
+ case '\\':
+ func->ucl_emitter_append_len ("\\\\", 2, func->ud);
+ break;
+ case '"':
+ func->ucl_emitter_append_len ("\\\"", 2, func->ud);
+ break;
+ }
+ len = 0;
+ c = ++p;
+ }
+ else {
+ p ++;
+ len ++;
+ }
+ size --;
+ }
+ if (len > 0) {
+ func->ucl_emitter_append_len (c, len, func->ud);
+ }
+ func->ucl_emitter_append_character ('"', 1, func->ud);
+}
+
+/**
+ * Write a single object to the buffer
+ * @param obj object to write
+ * @param buf target buffer
+ */
+static void
+ucl_elt_obj_write_json (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool compact)
+{
+ ucl_object_t *cur;
+ ucl_hash_iter_t it = NULL;
+
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, compact);
+ }
+ if (compact) {
+ func->ucl_emitter_append_character ('{', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("{\n", 2, func->ud);
+ }
+ while ((cur = ucl_hash_iterate (obj->value.ov, &it))) {
+ ucl_add_tabs (func, tabs + 1, compact);
+ if (cur->keylen > 0) {
+ ucl_elt_string_write_json (cur->key, cur->keylen, func);
+ }
+ else {
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ }
+ if (compact) {
+ func->ucl_emitter_append_character (':', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len (": ", 2, func->ud);
+ }
+ ucl_obj_write_json (cur, func, tabs + 1, false, compact);
+ if (ucl_hash_iter_has_next (it)) {
+ if (compact) {
+ func->ucl_emitter_append_character (',', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ }
+ }
+ else if (!compact) {
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ }
+ ucl_add_tabs (func, tabs, compact);
+ func->ucl_emitter_append_character ('}', 1, func->ud);
+}
+
+/**
+ * Write a single array to the buffer
+ * @param obj array to write
+ * @param buf target buffer
+ */
+static void
+ucl_elt_array_write_json (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool compact)
+{
+ ucl_object_t *cur = obj;
+
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, compact);
+ }
+ if (compact) {
+ func->ucl_emitter_append_character ('[', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("[\n", 2, func->ud);
+ }
+ while (cur) {
+ ucl_elt_write_json (cur, func, tabs + 1, true, compact);
+ if (cur->next != NULL) {
+ if (compact) {
+ func->ucl_emitter_append_character (',', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ }
+ }
+ else if (!compact) {
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ cur = cur->next;
+ }
+ ucl_add_tabs (func, tabs, compact);
+ func->ucl_emitter_append_character (']', 1, func->ud);
+}
+
+/**
+ * Emit a single element
+ * @param obj object
+ * @param buf buffer
+ */
+static void
+ucl_elt_write_json (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool compact)
+{
+ bool flag;
+
+ switch (obj->type) {
+ case UCL_INT:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, compact);
+ }
+ func->ucl_emitter_append_int (ucl_object_toint (obj), func->ud);
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, compact);
+ }
+ func->ucl_emitter_append_double (ucl_object_todouble (obj), func->ud);
+ break;
+ case UCL_BOOLEAN:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, compact);
+ }
+ flag = ucl_object_toboolean (obj);
+ if (flag) {
+ func->ucl_emitter_append_len ("true", 4, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("false", 5, func->ud);
+ }
+ break;
+ case UCL_STRING:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, compact);
+ }
+ ucl_elt_string_write_json (obj->value.sv, obj->len, func);
+ break;
+ case UCL_NULL:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, compact);
+ }
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ break;
+ case UCL_OBJECT:
+ ucl_elt_obj_write_json (obj, func, tabs, start_tabs, compact);
+ break;
+ case UCL_ARRAY:
+ ucl_elt_array_write_json (obj->value.av, func, tabs, start_tabs, compact);
+ break;
+ case UCL_USERDATA:
+ break;
+ }
+}
+
+/**
+ * Write a single object to the buffer
+ * @param obj object
+ * @param buf target buffer
+ */
+static void
+ucl_obj_write_json (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool compact)
+{
+ ucl_object_t *cur;
+ bool is_array = (obj->next != NULL);
+
+ if (is_array) {
+ /* This is an array actually */
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, compact);
+ }
+
+ if (compact) {
+ func->ucl_emitter_append_character ('[', 1, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("[\n", 2, func->ud);
+ }
+ cur = obj;
+ while (cur != NULL) {
+ ucl_elt_write_json (cur, func, tabs + 1, true, compact);
+ if (cur->next) {
+ func->ucl_emitter_append_character (',', 1, func->ud);
+ }
+ if (!compact) {
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ cur = cur->next;
+ }
+ ucl_add_tabs (func, tabs, compact);
+ func->ucl_emitter_append_character (']', 1, func->ud);
+ }
+ else {
+ ucl_elt_write_json (obj, func, tabs, start_tabs, compact);
+ }
+
+}
+
+/**
+ * Emit an object to json
+ * @param obj object
+ * @return json output (should be freed after using)
+ */
+static void
+ucl_object_emit_json (ucl_object_t *obj, bool compact, struct ucl_emitter_functions *func)
+{
+ ucl_obj_write_json (obj, func, 0, false, compact);
+}
+
+/**
+ * Write a single object to the buffer
+ * @param obj object to write
+ * @param buf target buffer
+ */
+static void
+ucl_elt_obj_write_config (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool is_top)
+{
+ ucl_object_t *cur, *cur_obj;
+ ucl_hash_iter_t it = NULL;
+
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, is_top);
+ }
+ if (!is_top) {
+ func->ucl_emitter_append_len ("{\n", 2, func->ud);
+ }
+
+ while ((cur = ucl_hash_iterate (obj->value.ov, &it))) {
+ LL_FOREACH (cur, cur_obj) {
+ ucl_add_tabs (func, tabs + 1, is_top);
+ if (cur_obj->flags & UCL_OBJECT_NEED_KEY_ESCAPE) {
+ ucl_elt_string_write_json (cur_obj->key, cur_obj->keylen, func);
+ }
+ else {
+ func->ucl_emitter_append_len (cur_obj->key, cur_obj->keylen, func->ud);
+ }
+ if (cur_obj->type != UCL_OBJECT && cur_obj->type != UCL_ARRAY) {
+ func->ucl_emitter_append_len (" = ", 3, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_character (' ', 1, func->ud);
+ }
+ ucl_elt_write_config (cur_obj, func,
+ is_top ? tabs : tabs + 1,
+ false, false, false);
+ if (cur_obj->type != UCL_OBJECT && cur_obj->type != UCL_ARRAY) {
+ func->ucl_emitter_append_len (";\n", 2, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ }
+ }
+
+ ucl_add_tabs (func, tabs, is_top);
+ if (!is_top) {
+ func->ucl_emitter_append_character ('}', 1, func->ud);
+ }
+}
+
+/**
+ * Write a single array to the buffer
+ * @param obj array to write
+ * @param buf target buffer
+ */
+static void
+ucl_elt_array_write_config (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool is_top)
+{
+ ucl_object_t *cur = obj;
+
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+
+ func->ucl_emitter_append_len ("[\n", 2, func->ud);
+ while (cur) {
+ ucl_elt_write_config (cur, func, tabs + 1, true, false, false);
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ cur = cur->next;
+ }
+ ucl_add_tabs (func, tabs, false);
+ func->ucl_emitter_append_character (']', 1, func->ud);
+}
+
+/**
+ * Emit a single element
+ * @param obj object
+ * @param buf buffer
+ */
+static void
+ucl_elt_write_config (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool is_top, bool expand_array)
+{
+ bool flag;
+
+ if (expand_array && obj->next != NULL) {
+ ucl_elt_array_write_config (obj, func, tabs, start_tabs, is_top);
+ }
+ else {
+ switch (obj->type) {
+ case UCL_INT:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ func->ucl_emitter_append_int (ucl_object_toint (obj), func->ud);
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ func->ucl_emitter_append_double (ucl_object_todouble (obj), func->ud);
+ break;
+ case UCL_BOOLEAN:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ flag = ucl_object_toboolean (obj);
+ if (flag) {
+ func->ucl_emitter_append_len ("true", 4, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("false", 5, func->ud);
+ }
+ break;
+ case UCL_STRING:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ ucl_elt_string_write_json (obj->value.sv, obj->len, func);
+ break;
+ case UCL_NULL:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ break;
+ case UCL_OBJECT:
+ ucl_elt_obj_write_config (obj, func, tabs, start_tabs, is_top);
+ break;
+ case UCL_ARRAY:
+ ucl_elt_array_write_config (obj->value.av, func, tabs, start_tabs, is_top);
+ break;
+ case UCL_USERDATA:
+ break;
+ }
+ }
+}
+
+/**
+ * Emit an object to rcl
+ * @param obj object
+ * @return rcl output (should be freed after using)
+ */
+static void
+ucl_object_emit_config (ucl_object_t *obj, struct ucl_emitter_functions *func)
+{
+ ucl_elt_write_config (obj, func, 0, false, true, true);
+}
+
+
+static void
+ucl_obj_write_yaml (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs)
+{
+ bool is_array = (obj->next != NULL);
+
+ if (is_array) {
+ ucl_elt_array_write_yaml (obj, func, tabs, start_tabs, false);
+ }
+ else {
+ ucl_elt_write_yaml (obj, func, tabs, start_tabs, false, true);
+ }
+}
+
+/**
+ * Write a single object to the buffer
+ * @param obj object to write
+ * @param buf target buffer
+ */
+static void
+ucl_elt_obj_write_yaml (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool is_top)
+{
+ ucl_object_t *cur;
+ ucl_hash_iter_t it = NULL;
+
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, is_top);
+ }
+ if (!is_top) {
+ func->ucl_emitter_append_len ("{\n", 2, func->ud);
+ }
+
+ while ((cur = ucl_hash_iterate (obj->value.ov, &it))) {
+ ucl_add_tabs (func, tabs + 1, is_top);
+ if (cur->keylen > 0) {
+ ucl_elt_string_write_json (cur->key, cur->keylen, func);
+ }
+ else {
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ }
+ func->ucl_emitter_append_len (": ", 2, func->ud);
+ ucl_obj_write_yaml (cur, func, is_top ? tabs : tabs + 1, false);
+ if (ucl_hash_iter_has_next(it)) {
+ if (!is_top) {
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ }
+ else {
+ func->ucl_emitter_append_character ('\n', 1, func->ud);
+ }
+ }
+
+ ucl_add_tabs (func, tabs, is_top);
+ if (!is_top) {
+ func->ucl_emitter_append_character ('}', 1, func->ud);
+ }
+}
+
+/**
+ * Write a single array to the buffer
+ * @param obj array to write
+ * @param buf target buffer
+ */
+static void
+ucl_elt_array_write_yaml (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool is_top)
+{
+ ucl_object_t *cur = obj;
+
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+
+ func->ucl_emitter_append_len ("[\n", 2, func->ud);
+ while (cur) {
+ ucl_elt_write_yaml (cur, func, tabs + 1, true, false, false);
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ cur = cur->next;
+ }
+ ucl_add_tabs (func, tabs, false);
+ func->ucl_emitter_append_character (']', 1, func->ud);
+}
+
+/**
+ * Emit a single element
+ * @param obj object
+ * @param buf buffer
+ */
+static void
+ucl_elt_write_yaml (ucl_object_t *obj, struct ucl_emitter_functions *func,
+ unsigned int tabs, bool start_tabs, bool is_top, bool expand_array)
+{
+ bool flag;
+
+ if (expand_array && obj->next != NULL ) {
+ ucl_elt_array_write_yaml (obj, func, tabs, start_tabs, is_top);
+ }
+ else {
+ switch (obj->type) {
+ case UCL_INT:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ func->ucl_emitter_append_int (ucl_object_toint (obj), func->ud);
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ func->ucl_emitter_append_double (ucl_object_todouble (obj), func->ud);
+ break;
+ case UCL_BOOLEAN:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ flag = ucl_object_toboolean (obj);
+ if (flag) {
+ func->ucl_emitter_append_len ("true", 4, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("false", 5, func->ud);
+ }
+ break;
+ case UCL_STRING:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ ucl_elt_string_write_json (obj->value.sv, obj->len, func);
+ break;
+ case UCL_NULL:
+ if (start_tabs) {
+ ucl_add_tabs (func, tabs, false);
+ }
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ break;
+ case UCL_OBJECT:
+ ucl_elt_obj_write_yaml (obj, func, tabs, start_tabs, is_top);
+ break;
+ case UCL_ARRAY:
+ ucl_elt_array_write_yaml (obj->value.av, func, tabs, start_tabs, is_top);
+ break;
+ case UCL_USERDATA:
+ break;
+ }
+ }
+}
+
+/**
+ * Emit an object to rcl
+ * @param obj object
+ * @return rcl output (should be freed after using)
+ */
+static void
+ucl_object_emit_yaml (ucl_object_t *obj, struct ucl_emitter_functions *func)
+{
+ ucl_elt_write_yaml (obj, func, 0, false, true, true);
+}
+
+/*
+ * Generic utstring output
+ */
+static int
+ucl_utstring_append_character (unsigned char c, size_t len, void *ud)
+{
+ UT_string *buf = ud;
+
+ if (len == 1) {
+ utstring_append_c (buf, c);
+ }
+ else {
+ utstring_reserve (buf, len);
+ memset (&buf->d[buf->i], c, len);
+ buf->i += len;
+ buf->d[buf->i] = '\0';
+ }
+
+ return 0;
+}
+
+static int
+ucl_utstring_append_len (const unsigned char *str, size_t len, void *ud)
+{
+ UT_string *buf = ud;
+
+ utstring_append_len (buf, str, len);
+
+ return 0;
+}
+
+static int
+ucl_utstring_append_int (int64_t val, void *ud)
+{
+ UT_string *buf = ud;
+
+ utstring_printf (buf, "%jd", (intmax_t)val);
+ return 0;
+}
+
+static int
+ucl_utstring_append_double (double val, void *ud)
+{
+ UT_string *buf = ud;
+ const double delta = 0.0000001;
+
+ if (val == (double)(int)val) {
+ utstring_printf (buf, "%.1lf", val);
+ }
+ else if (fabs (val - (double)(int)val) < delta) {
+ /* Write at maximum precision */
+ utstring_printf (buf, "%.*lg", DBL_DIG, val);
+ }
+ else {
+ utstring_printf (buf, "%lf", val);
+ }
+
+ return 0;
+}
+
+
+unsigned char *
+ucl_object_emit (ucl_object_t *obj, enum ucl_emitter emit_type)
+{
+ UT_string *buf = NULL;
+ unsigned char *res = NULL;
+ struct ucl_emitter_functions func = {
+ .ucl_emitter_append_character = ucl_utstring_append_character,
+ .ucl_emitter_append_len = ucl_utstring_append_len,
+ .ucl_emitter_append_int = ucl_utstring_append_int,
+ .ucl_emitter_append_double = ucl_utstring_append_double
+ };
+
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ utstring_new (buf);
+ func.ud = buf;
+
+ if (buf != NULL) {
+ if (emit_type == UCL_EMIT_JSON) {
+ ucl_object_emit_json (obj, false, &func);
+ }
+ else if (emit_type == UCL_EMIT_JSON_COMPACT) {
+ ucl_object_emit_json (obj, true, &func);
+ }
+ else if (emit_type == UCL_EMIT_YAML) {
+ ucl_object_emit_yaml (obj, &func);
+ }
+ else {
+ ucl_object_emit_config (obj, &func);
+ }
+
+ res = utstring_body (buf);
+ free (buf);
+ }
+
+ return res;
+}
+
+bool
+ucl_object_emit_full (ucl_object_t *obj, enum ucl_emitter emit_type,
+ struct ucl_emitter_functions *emitter)
+{
+ if (emit_type == UCL_EMIT_JSON) {
+ ucl_object_emit_json (obj, false, emitter);
+ }
+ else if (emit_type == UCL_EMIT_JSON_COMPACT) {
+ ucl_object_emit_json (obj, true, emitter);
+ }
+ else if (emit_type == UCL_EMIT_YAML) {
+ ucl_object_emit_yaml (obj, emitter);
+ }
+ else {
+ ucl_object_emit_config (obj, emitter);
+ }
+
+ /* XXX: need some error checks here */
+ return true;
+}
+
+
+unsigned char *
+ucl_object_emit_single_json (ucl_object_t *obj)
+{
+ UT_string *buf = NULL;
+ unsigned char *res = NULL;
+
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ utstring_new (buf);
+
+ if (buf != NULL) {
+ switch (obj->type) {
+ case UCL_OBJECT:
+ ucl_utstring_append_len ("object", 6, buf);
+ break;
+ case UCL_ARRAY:
+ ucl_utstring_append_len ("array", 5, buf);
+ break;
+ case UCL_INT:
+ ucl_utstring_append_int (obj->value.iv, buf);
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ ucl_utstring_append_double (obj->value.dv, buf);
+ break;
+ case UCL_NULL:
+ ucl_utstring_append_len ("null", 4, buf);
+ break;
+ case UCL_BOOLEAN:
+ if (obj->value.iv) {
+ ucl_utstring_append_len ("true", 4, buf);
+ }
+ else {
+ ucl_utstring_append_len ("false", 5, buf);
+ }
+ break;
+ case UCL_STRING:
+ ucl_utstring_append_len (obj->value.sv, obj->len, buf);
+ break;
+ case UCL_USERDATA:
+ ucl_utstring_append_len ("userdata", 8, buf);
+ break;
+ }
+ res = utstring_body (buf);
+ free (buf);
+ }
+
+ return res;
+}
diff --git a/src/ucl_hash.c b/src/ucl_hash.c
new file mode 100644
index 000000000000..a3711deb8539
--- /dev/null
+++ b/src/ucl_hash.c
@@ -0,0 +1,120 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucl_hash.h"
+#include "utlist.h"
+
+ucl_hash_t*
+ucl_hash_create (void)
+{
+ ucl_hash_t *new;
+
+ new = UCL_ALLOC (sizeof (ucl_hash_t));
+ if (new != NULL) {
+ new->buckets = NULL;
+ }
+ return new;
+}
+
+void ucl_hash_destroy (ucl_hash_t* hashlin, ucl_hash_free_func *func)
+{
+ ucl_hash_node_t *elt, *tmp;
+
+ HASH_ITER (hh, hashlin->buckets, elt, tmp) {
+ HASH_DELETE (hh, hashlin->buckets, elt);
+ if (func) {
+ func (elt->data);
+ }
+ UCL_FREE (sizeof (ucl_hash_node_t), elt);
+ }
+ UCL_FREE (sizeof (ucl_hash_t), hashlin);
+}
+
+void
+ucl_hash_insert (ucl_hash_t* hashlin, ucl_object_t *obj, const char *key, unsigned keylen)
+{
+ ucl_hash_node_t *node;
+
+ node = UCL_ALLOC (sizeof (ucl_hash_node_t));
+ node->data = obj;
+ HASH_ADD_KEYPTR (hh, hashlin->buckets, key, keylen, node);
+}
+
+void*
+ucl_hash_iterate (ucl_hash_t *hashlin, ucl_hash_iter_t *iter)
+{
+ ucl_hash_node_t *elt = *iter;
+
+ if (elt == NULL) {
+ if (hashlin == NULL || hashlin->buckets == NULL) {
+ return NULL;
+ }
+ elt = hashlin->buckets;
+ if (elt == NULL) {
+ return NULL;
+ }
+ }
+ else if (elt == hashlin->buckets) {
+ return NULL;
+ }
+
+ *iter = elt->hh.next ? elt->hh.next : hashlin->buckets;
+ return elt->data;
+}
+
+bool
+ucl_hash_iter_has_next (ucl_hash_iter_t iter)
+{
+ ucl_hash_node_t *elt = iter;
+
+ return (elt == NULL || elt->hh.prev != NULL);
+}
+
+
+ucl_object_t*
+ucl_hash_search (ucl_hash_t* hashlin, const char *key, unsigned keylen)
+{
+ ucl_hash_node_t *found;
+
+ if (hashlin == NULL) {
+ return NULL;
+ }
+ HASH_FIND (hh, hashlin->buckets, key, keylen, found);
+
+ if (found) {
+ return found->data;
+ }
+ return NULL;
+}
+
+void
+ucl_hash_delete (ucl_hash_t* hashlin, ucl_object_t *obj)
+{
+ ucl_hash_node_t *found;
+
+ HASH_FIND (hh, hashlin->buckets, obj->key, obj->keylen, found);
+
+ if (found) {
+ HASH_DELETE (hh, hashlin->buckets, found);
+ }
+}
diff --git a/src/ucl_hash.h b/src/ucl_hash.h
new file mode 100644
index 000000000000..5c9b851b9f29
--- /dev/null
+++ b/src/ucl_hash.h
@@ -0,0 +1,91 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __UCL_HASH_H
+#define __UCL_HASH_H
+
+#include "ucl.h"
+#include "uthash.h"
+
+/******************************************************************************/
+
+typedef struct ucl_hash_node_s
+{
+ ucl_object_t *data;
+ UT_hash_handle hh;
+} ucl_hash_node_t;
+
+typedef int ucl_hash_cmp_func (const void* void_a, const void* void_b);
+typedef void ucl_hash_free_func (void *ptr);
+typedef void* ucl_hash_iter_t;
+
+
+/**
+ * Linear chained hashtable.
+ */
+typedef struct ucl_hash_struct
+{
+ ucl_hash_node_t *buckets; /**< array of hash buckets. One list for each hash modulus. */
+} ucl_hash_t;
+
+
+/**
+ * Initializes the hashtable.
+ */
+ucl_hash_t* ucl_hash_create (void);
+
+/**
+ * Deinitializes the hashtable.
+ */
+void ucl_hash_destroy (ucl_hash_t* hashlin, ucl_hash_free_func *func);
+
+/**
+ * Inserts an element in the the hashtable.
+ */
+void ucl_hash_insert (ucl_hash_t* hashlin, ucl_object_t *obj, const char *key, unsigned keylen);
+
+/**
+ * Delete an element from the the hashtable.
+ */
+void ucl_hash_delete (ucl_hash_t* hashlin, ucl_object_t *obj);
+
+/**
+ * Searches an element in the hashtable.
+ */
+ucl_object_t* ucl_hash_search (ucl_hash_t* hashlin, const char *key, unsigned keylen);
+
+
+/**
+ * Iterate over hash table
+ * @param hashlin hash
+ * @param iter iterator (must be NULL on first iteration)
+ * @return the next object
+ */
+void* ucl_hash_iterate (ucl_hash_t *hashlin, ucl_hash_iter_t *iter);
+
+/**
+ * Check whether an iterator has next element
+ */
+bool ucl_hash_iter_has_next (ucl_hash_iter_t iter);
+
+#endif
diff --git a/src/ucl_internal.h b/src/ucl_internal.h
new file mode 100644
index 000000000000..e2a6d52fa677
--- /dev/null
+++ b/src/ucl_internal.h
@@ -0,0 +1,292 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UCL_INTERNAL_H_
+#define UCL_INTERNAL_H_
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "utlist.h"
+#include "utstring.h"
+#include "uthash.h"
+#include "ucl.h"
+#include "ucl_hash.h"
+#include "xxhash.h"
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#endif
+
+/**
+ * @file rcl_internal.h
+ * Internal structures and functions of UCL library
+ */
+
+#define UCL_MAX_RECURSION 16
+#define UCL_TRASH_KEY 0
+#define UCL_TRASH_VALUE 1
+
+enum ucl_parser_state {
+ UCL_STATE_INIT = 0,
+ UCL_STATE_OBJECT,
+ UCL_STATE_ARRAY,
+ UCL_STATE_KEY,
+ UCL_STATE_VALUE,
+ UCL_STATE_AFTER_VALUE,
+ UCL_STATE_ARRAY_VALUE,
+ UCL_STATE_SCOMMENT,
+ UCL_STATE_MCOMMENT,
+ UCL_STATE_MACRO_NAME,
+ UCL_STATE_MACRO,
+ UCL_STATE_ERROR
+};
+
+enum ucl_character_type {
+ UCL_CHARACTER_DENIED = 0,
+ UCL_CHARACTER_KEY = 1,
+ UCL_CHARACTER_KEY_START = 1 << 1,
+ UCL_CHARACTER_WHITESPACE = 1 << 2,
+ UCL_CHARACTER_WHITESPACE_UNSAFE = 1 << 3,
+ UCL_CHARACTER_VALUE_END = 1 << 4,
+ UCL_CHARACTER_VALUE_STR = 1 << 5,
+ UCL_CHARACTER_VALUE_DIGIT = 1 << 6,
+ UCL_CHARACTER_VALUE_DIGIT_START = 1 << 7,
+ UCL_CHARACTER_ESCAPE = 1 << 8,
+ UCL_CHARACTER_KEY_SEP = 1 << 9,
+ UCL_CHARACTER_JSON_UNSAFE = 1 << 10,
+ UCL_CHARACTER_UCL_UNSAFE = 1 << 11
+};
+
+struct ucl_macro {
+ char *name;
+ ucl_macro_handler handler;
+ void* ud;
+ UT_hash_handle hh;
+};
+
+struct ucl_stack {
+ ucl_object_t *obj;
+ struct ucl_stack *next;
+ int level;
+};
+
+struct ucl_chunk {
+ const unsigned char *begin;
+ const unsigned char *end;
+ const unsigned char *pos;
+ size_t remain;
+ unsigned int line;
+ unsigned int column;
+ struct ucl_chunk *next;
+};
+
+#ifdef HAVE_OPENSSL
+struct ucl_pubkey {
+ EVP_PKEY *key;
+ struct ucl_pubkey *next;
+};
+#else
+struct ucl_pubkey {
+ struct ucl_pubkey *next;
+};
+#endif
+
+struct ucl_variable {
+ char *var;
+ char *value;
+ size_t var_len;
+ size_t value_len;
+ struct ucl_variable *next;
+};
+
+struct ucl_parser {
+ enum ucl_parser_state state;
+ enum ucl_parser_state prev_state;
+ unsigned int recursion;
+ int flags;
+ ucl_object_t *top_obj;
+ ucl_object_t *cur_obj;
+ struct ucl_macro *macroes;
+ struct ucl_stack *stack;
+ struct ucl_chunk *chunks;
+ struct ucl_pubkey *keys;
+ struct ucl_variable *variables;
+ UT_string *err;
+};
+
+/**
+ * Unescape json string inplace
+ * @param str
+ */
+size_t ucl_unescape_json_string (char *str, size_t len);
+
+/**
+ * Handle include macro
+ * @param data include data
+ * @param len length of data
+ * @param ud user data
+ * @param err error ptr
+ * @return
+ */
+bool ucl_include_handler (const unsigned char *data, size_t len, void* ud);
+
+bool ucl_try_include_handler (const unsigned char *data, size_t len, void* ud);
+
+/**
+ * Handle includes macro
+ * @param data include data
+ * @param len length of data
+ * @param ud user data
+ * @param err error ptr
+ * @return
+ */
+bool ucl_includes_handler (const unsigned char *data, size_t len, void* ud);
+
+size_t ucl_strlcpy (char *dst, const char *src, size_t siz);
+size_t ucl_strlcpy_unsafe (char *dst, const char *src, size_t siz);
+size_t ucl_strlcpy_tolower (char *dst, const char *src, size_t siz);
+
+
+#ifdef __GNUC__
+static inline void
+ucl_create_err (UT_string **err, const char *fmt, ...)
+__attribute__ (( format( printf, 2, 3) ));
+#endif
+
+static inline void
+ucl_create_err (UT_string **err, const char *fmt, ...)
+
+{
+ if (*err == NULL) {
+ utstring_new (*err);
+ va_list ap;
+ va_start (ap, fmt);
+ utstring_printf_va (*err, fmt, ap);
+ va_end (ap);
+ }
+}
+
+/**
+ * Check whether a given string contains a boolean value
+ * @param obj object to set
+ * @param start start of a string
+ * @param len length of a string
+ * @return true if a string is a boolean value
+ */
+static inline bool
+ucl_maybe_parse_boolean (ucl_object_t *obj, const unsigned char *start, size_t len)
+{
+ const unsigned char *p = start;
+ bool ret = false, val = false;
+
+ if (len == 5) {
+ if ((p[0] == 'f' || p[0] == 'F') && strncasecmp (p, "false", 5) == 0) {
+ ret = true;
+ val = false;
+ }
+ }
+ else if (len == 4) {
+ if ((p[0] == 't' || p[0] == 'T') && strncasecmp (p, "true", 4) == 0) {
+ ret = true;
+ val = true;
+ }
+ }
+ else if (len == 3) {
+ if ((p[0] == 'y' || p[0] == 'Y') && strncasecmp (p, "yes", 3) == 0) {
+ ret = true;
+ val = true;
+ }
+ else if ((p[0] == 'o' || p[0] == 'O') && strncasecmp (p, "off", 3) == 0) {
+ ret = true;
+ val = false;
+ }
+ }
+ else if (len == 2) {
+ if ((p[0] == 'n' || p[0] == 'N') && strncasecmp (p, "no", 2) == 0) {
+ ret = true;
+ val = false;
+ }
+ else if ((p[0] == 'o' || p[0] == 'O') && strncasecmp (p, "on", 2) == 0) {
+ ret = true;
+ val = true;
+ }
+ }
+
+ if (ret) {
+ obj->type = UCL_BOOLEAN;
+ obj->value.iv = val;
+ }
+
+ return ret;
+}
+
+/**
+ * Check numeric string
+ * @param obj object to set if a string is numeric
+ * @param start start of string
+ * @param end end of string
+ * @param pos position where parsing has stopped
+ * @param allow_double allow parsing of floating point values
+ * @return 0 if string is numeric and error code (EINVAL or ERANGE) in case of conversion error
+ */
+int ucl_maybe_parse_number (ucl_object_t *obj,
+ const char *start, const char *end, const char **pos, bool allow_double, bool number_bytes);
+
+
+static inline ucl_object_t *
+ucl_hash_search_obj (ucl_hash_t* hashlin, ucl_object_t *obj)
+{
+ return (ucl_object_t *)ucl_hash_search (hashlin, obj->key, obj->keylen);
+}
+
+static inline ucl_hash_t *
+ucl_hash_insert_object (ucl_hash_t *hashlin, ucl_object_t *obj) UCL_WARN_UNUSED_RESULT;
+
+static inline ucl_hash_t *
+ucl_hash_insert_object (ucl_hash_t *hashlin, ucl_object_t *obj)
+{
+ if (hashlin == NULL) {
+ hashlin = ucl_hash_create ();
+ }
+ ucl_hash_insert (hashlin, obj, obj->key, obj->keylen);
+
+ return hashlin;
+}
+
+/**
+ * Emit a single object to string
+ * @param obj
+ * @return
+ */
+unsigned char * ucl_object_emit_single_json (ucl_object_t *obj);
+
+#endif /* UCL_INTERNAL_H_ */
diff --git a/src/ucl_parser.c b/src/ucl_parser.c
new file mode 100644
index 000000000000..7aa218152cd1
--- /dev/null
+++ b/src/ucl_parser.c
@@ -0,0 +1,1872 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "ucl_chartable.h"
+
+/**
+ * @file rcl_parser.c
+ * The implementation of rcl parser
+ */
+
+struct ucl_parser_saved_state {
+ unsigned int line;
+ unsigned int column;
+ size_t remain;
+ const unsigned char *pos;
+};
+
+/**
+ * Move up to len characters
+ * @param parser
+ * @param begin
+ * @param len
+ * @return new position in chunk
+ */
+#define ucl_chunk_skipc(chunk, p) do{ \
+ if (*(p) == '\n') { \
+ (chunk)->line ++; \
+ (chunk)->column = 0; \
+ } \
+ else (chunk)->column ++; \
+ (p++); \
+ (chunk)->pos ++; \
+ (chunk)->remain --; \
+ } while (0)
+
+/**
+ * Save parser state
+ * @param chunk
+ * @param s
+ */
+static inline void
+ucl_chunk_save_state (struct ucl_chunk *chunk, struct ucl_parser_saved_state *s)
+{
+ s->column = chunk->column;
+ s->pos = chunk->pos;
+ s->line = chunk->line;
+ s->remain = chunk->remain;
+}
+
+/**
+ * Restore parser state
+ * @param chunk
+ * @param s
+ */
+static inline void
+ucl_chunk_restore_state (struct ucl_chunk *chunk, struct ucl_parser_saved_state *s)
+{
+ chunk->column = s->column;
+ chunk->pos = s->pos;
+ chunk->line = s->line;
+ chunk->remain = s->remain;
+}
+
+static inline void
+ucl_set_err (struct ucl_chunk *chunk, int code, const char *str, UT_string **err)
+{
+ if (chunk->pos < chunk->end) {
+ if (isgraph (*chunk->pos)) {
+ ucl_create_err (err, "error on line %d at column %d: '%s', character: '%c'",
+ chunk->line, chunk->column, str, *chunk->pos);
+ }
+ else {
+ ucl_create_err (err, "error on line %d at column %d: '%s', character: '0x%02x'",
+ chunk->line, chunk->column, str, (int)*chunk->pos);
+ }
+ }
+ else {
+ ucl_create_err (err, "error at the end of chunk: %s", str);
+ }
+}
+
+/**
+ * Skip all comments from the current pos resolving nested and multiline comments
+ * @param parser
+ * @return
+ */
+static bool
+ucl_skip_comments (struct ucl_parser *parser)
+{
+ struct ucl_chunk *chunk = parser->chunks;
+ const unsigned char *p;
+ int comments_nested = 0;
+
+ p = chunk->pos;
+
+start:
+ if (*p == '#') {
+ if (parser->state != UCL_STATE_SCOMMENT &&
+ parser->state != UCL_STATE_MCOMMENT) {
+ while (p < chunk->end) {
+ if (*p == '\n') {
+ ucl_chunk_skipc (chunk, p);
+ goto start;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ }
+ }
+ else if (*p == '/' && chunk->remain >= 2) {
+ if (p[1] == '*') {
+ ucl_chunk_skipc (chunk, p);
+ comments_nested ++;
+ ucl_chunk_skipc (chunk, p);
+
+ while (p < chunk->end) {
+ if (*p == '*') {
+ ucl_chunk_skipc (chunk, p);
+ if (*p == '/') {
+ comments_nested --;
+ if (comments_nested == 0) {
+ ucl_chunk_skipc (chunk, p);
+ goto start;
+ }
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (p[0] == '/' && chunk->remain >= 2 && p[1] == '*') {
+ comments_nested ++;
+ ucl_chunk_skipc (chunk, p);
+ ucl_chunk_skipc (chunk, p);
+ continue;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ if (comments_nested != 0) {
+ ucl_set_err (chunk, UCL_ENESTED, "unfinished multiline comment", &parser->err);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Return multiplier for a character
+ * @param c multiplier character
+ * @param is_bytes if true use 1024 multiplier
+ * @return multiplier
+ */
+static inline unsigned long
+ucl_lex_num_multiplier (const unsigned char c, bool is_bytes) {
+ const struct {
+ char c;
+ long mult_normal;
+ long mult_bytes;
+ } multipliers[] = {
+ {'m', 1000 * 1000, 1024 * 1024},
+ {'k', 1000, 1024},
+ {'g', 1000 * 1000 * 1000, 1024 * 1024 * 1024}
+ };
+ int i;
+
+ for (i = 0; i < 3; i ++) {
+ if (tolower (c) == multipliers[i].c) {
+ if (is_bytes) {
+ return multipliers[i].mult_bytes;
+ }
+ return multipliers[i].mult_normal;
+ }
+ }
+
+ return 1;
+}
+
+
+/**
+ * Return multiplier for time scaling
+ * @param c
+ * @return
+ */
+static inline double
+ucl_lex_time_multiplier (const unsigned char c) {
+ const struct {
+ char c;
+ double mult;
+ } multipliers[] = {
+ {'m', 60},
+ {'h', 60 * 60},
+ {'d', 60 * 60 * 24},
+ {'w', 60 * 60 * 24 * 7},
+ {'y', 60 * 60 * 24 * 7 * 365}
+ };
+ int i;
+
+ for (i = 0; i < 5; i ++) {
+ if (tolower (c) == multipliers[i].c) {
+ return multipliers[i].mult;
+ }
+ }
+
+ return 1;
+}
+
+/**
+ * Return true if a character is a end of an atom
+ * @param c
+ * @return
+ */
+static inline bool
+ucl_lex_is_atom_end (const unsigned char c)
+{
+ return ucl_test_character (c, UCL_CHARACTER_VALUE_END);
+}
+
+static inline bool
+ucl_lex_is_comment (const unsigned char c1, const unsigned char c2)
+{
+ if (c1 == '/') {
+ if (c2 == '*') {
+ return true;
+ }
+ }
+ else if (c1 == '#') {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Check variable found
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param out_len
+ * @param strict
+ * @param found
+ * @return
+ */
+static inline const char *
+ucl_check_variable_safe (struct ucl_parser *parser, const char *ptr, size_t remain,
+ size_t *out_len, bool strict, bool *found)
+{
+ struct ucl_variable *var;
+
+ LL_FOREACH (parser->variables, var) {
+ if (strict) {
+ if (remain == var->var_len) {
+ if (memcmp (ptr, var->var, var->var_len) == 0) {
+ *out_len += var->value_len;
+ *found = true;
+ return (ptr + var->var_len);
+ }
+ }
+ }
+ else {
+ if (remain >= var->var_len) {
+ if (memcmp (ptr, var->var, var->var_len) == 0) {
+ *out_len += var->value_len;
+ *found = true;
+ return (ptr + var->var_len);
+ }
+ }
+ }
+ }
+
+ return ptr;
+}
+
+/**
+ * Check for a variable in a given string
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param out_len
+ * @param vars_found
+ * @return
+ */
+static const char *
+ucl_check_variable (struct ucl_parser *parser, const char *ptr, size_t remain, size_t *out_len, bool *vars_found)
+{
+ const char *p, *end, *ret = ptr;
+ bool found = false;
+
+ if (*ptr == '{') {
+ /* We need to match the variable enclosed in braces */
+ p = ptr + 1;
+ end = ptr + remain;
+ while (p < end) {
+ if (*p == '}') {
+ ret = ucl_check_variable_safe (parser, ptr + 1, p - ptr - 1, out_len, true, &found);
+ if (found) {
+ /* {} must be excluded actually */
+ ret ++;
+ if (!*vars_found) {
+ *vars_found = true;
+ }
+ }
+ else {
+ *out_len += 2;
+ }
+ break;
+ }
+ p ++;
+ }
+ }
+ else if (*ptr != '$') {
+ /* Not count escaped dollar sign */
+ ret = ucl_check_variable_safe (parser, ptr, remain, out_len, false, &found);
+ if (found && !*vars_found) {
+ *vars_found = true;
+ }
+ if (!found) {
+ (*out_len) ++;
+ }
+ }
+ else {
+ ret ++;
+ (*out_len) ++;
+ }
+
+ return ret;
+}
+
+/**
+ * Expand a single variable
+ * @param parser
+ * @param ptr
+ * @param remain
+ * @param dest
+ * @return
+ */
+static const char *
+ucl_expand_single_variable (struct ucl_parser *parser, const char *ptr,
+ size_t remain, unsigned char **dest)
+{
+ unsigned char *d = *dest;
+ const char *p = ptr + 1, *ret;
+ struct ucl_variable *var;
+ bool found = false;
+
+ ret = ptr + 1;
+ remain --;
+
+ if (*p == '$') {
+ *d++ = *p++;
+ *dest = d;
+ return p;
+ }
+ else if (*p == '{') {
+ p ++;
+ ret += 2;
+ remain -= 2;
+ }
+
+ LL_FOREACH (parser->variables, var) {
+ if (remain >= var->var_len) {
+ if (memcmp (p, var->var, var->var_len) == 0) {
+ memcpy (d, var->value, var->value_len);
+ ret += var->var_len;
+ d += var->value_len;
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ memcpy (d, ptr, 2);
+ d += 2;
+ ret --;
+ }
+
+ *dest = d;
+ return ret;
+}
+
+/**
+ * Expand variables in string
+ * @param parser
+ * @param dst
+ * @param src
+ * @param in_len
+ * @return
+ */
+static ssize_t
+ucl_expand_variable (struct ucl_parser *parser, unsigned char **dst,
+ const char *src, size_t in_len)
+{
+ const char *p, *end = src + in_len;
+ unsigned char *d;
+ size_t out_len = 0;
+ bool vars_found = false;
+
+ p = src;
+ while (p != end) {
+ if (*p == '$') {
+ p = ucl_check_variable (parser, p + 1, end - p - 1, &out_len, &vars_found);
+ }
+ else {
+ p ++;
+ out_len ++;
+ }
+ }
+
+ if (!vars_found) {
+ /* Trivial case */
+ *dst = NULL;
+ return in_len;
+ }
+
+ *dst = UCL_ALLOC (out_len + 1);
+ if (*dst == NULL) {
+ return in_len;
+ }
+
+ d = *dst;
+ p = src;
+ while (p != end) {
+ if (*p == '$') {
+ p = ucl_expand_single_variable (parser, p, end - p, &d);
+ }
+ else {
+ *d++ = *p++;
+ }
+ }
+
+ *d = '\0';
+
+ return out_len;
+}
+
+/**
+ * Store or copy pointer to the trash stack
+ * @param parser parser object
+ * @param src src string
+ * @param dst destination buffer (trash stack pointer)
+ * @param dst_const const destination pointer (e.g. value of object)
+ * @param in_len input length
+ * @param need_unescape need to unescape source (and copy it)
+ * @param need_lowercase need to lowercase value (and copy)
+ * @param need_expand need to expand variables (and copy as well)
+ * @return output length (excluding \0 symbol)
+ */
+static inline ssize_t
+ucl_copy_or_store_ptr (struct ucl_parser *parser,
+ const unsigned char *src, unsigned char **dst,
+ const char **dst_const, size_t in_len,
+ bool need_unescape, bool need_lowercase, bool need_expand)
+{
+ ssize_t ret = -1, tret;
+ unsigned char *tmp;
+
+ if (need_unescape || need_lowercase ||
+ (need_expand && parser->variables != NULL) ||
+ !(parser->flags & UCL_PARSER_ZEROCOPY)) {
+ /* Copy string */
+ *dst = UCL_ALLOC (in_len + 1);
+ if (*dst == NULL) {
+ ucl_set_err (parser->chunks, 0, "cannot allocate memory for a string", &parser->err);
+ return false;
+ }
+ if (need_lowercase) {
+ ret = ucl_strlcpy_tolower (*dst, src, in_len + 1);
+ }
+ else {
+ ret = ucl_strlcpy_unsafe (*dst, src, in_len + 1);
+ }
+
+ if (need_unescape) {
+ ret = ucl_unescape_json_string (*dst, ret);
+ }
+ if (need_expand) {
+ tmp = *dst;
+ tret = ret;
+ ret = ucl_expand_variable (parser, dst, tmp, ret);
+ if (*dst == NULL) {
+ /* Nothing to expand */
+ *dst = tmp;
+ ret = tret;
+ }
+ }
+ *dst_const = *dst;
+ }
+ else {
+ *dst_const = src;
+ ret = in_len;
+ }
+
+ return ret;
+}
+
+/**
+ * Create and append an object at the specified level
+ * @param parser
+ * @param is_array
+ * @param level
+ * @return
+ */
+static inline ucl_object_t *
+ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_array, int level)
+{
+ struct ucl_stack *st;
+
+ if (!is_array) {
+ if (obj == NULL) {
+ obj = ucl_object_typed_new (UCL_OBJECT);
+ }
+ else {
+ obj->type = UCL_OBJECT;
+ }
+ obj->value.ov = ucl_hash_create ();
+ parser->state = UCL_STATE_KEY;
+ }
+ else {
+ if (obj == NULL) {
+ obj = ucl_object_typed_new (UCL_ARRAY);
+ }
+ else {
+ obj->type = UCL_ARRAY;
+ }
+ parser->state = UCL_STATE_VALUE;
+ }
+
+ st = UCL_ALLOC (sizeof (struct ucl_stack));
+ st->obj = obj;
+ st->level = level;
+ LL_PREPEND (parser->stack, st);
+ parser->cur_obj = obj;
+
+ return obj;
+}
+
+int
+ucl_maybe_parse_number (ucl_object_t *obj,
+ const char *start, const char *end, const char **pos, bool allow_double, bool number_bytes)
+{
+ const char *p = start, *c = start;
+ char *endptr;
+ bool got_dot = false, got_exp = false, need_double = false,
+ is_date = false, valid_start = false, is_hex = false,
+ is_neg = false;
+ double dv = 0;
+ int64_t lv = 0;
+
+ if (*p == '-') {
+ is_neg = true;
+ c ++;
+ p ++;
+ }
+ while (p < end) {
+ if (is_hex && isxdigit (*p)) {
+ p ++;
+ }
+ else if (isdigit (*p)) {
+ valid_start = true;
+ p ++;
+ }
+ else if (!is_hex && (*p == 'x' || *p == 'X')) {
+ is_hex = true;
+ allow_double = false;
+ c = p + 1;
+ }
+ else if (allow_double) {
+ if (p == c) {
+ /* Empty digits sequence, not a number */
+ *pos = start;
+ return EINVAL;
+ }
+ else if (*p == '.') {
+ if (got_dot) {
+ /* Double dots, not a number */
+ *pos = start;
+ return EINVAL;
+ }
+ else {
+ got_dot = true;
+ need_double = true;
+ p ++;
+ }
+ }
+ else if (*p == 'e' || *p == 'E') {
+ if (got_exp) {
+ /* Double exp, not a number */
+ *pos = start;
+ return EINVAL;
+ }
+ else {
+ got_exp = true;
+ need_double = true;
+ p ++;
+ if (p >= end) {
+ *pos = start;
+ return EINVAL;
+ }
+ if (!isdigit (*p) && *p != '+' && *p != '-') {
+ /* Wrong exponent sign */
+ *pos = start;
+ return EINVAL;
+ }
+ else {
+ p ++;
+ }
+ }
+ }
+ else {
+ /* Got the end of the number, need to check */
+ break;
+ }
+ }
+ else {
+ break;
+ }
+ }
+
+ if (!valid_start) {
+ *pos = start;
+ return EINVAL;
+ }
+
+ errno = 0;
+ if (need_double) {
+ dv = strtod (c, &endptr);
+ }
+ else {
+ if (is_hex) {
+ lv = strtoimax (c, &endptr, 16);
+ }
+ else {
+ lv = strtoimax (c, &endptr, 10);
+ }
+ }
+ if (errno == ERANGE) {
+ *pos = start;
+ return ERANGE;
+ }
+
+ /* Now check endptr */
+ if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0') {
+ p = endptr;
+ goto set_obj;
+ }
+
+ if (endptr < end && endptr != start) {
+ p = endptr;
+ switch (*p) {
+ case 'm':
+ case 'M':
+ case 'g':
+ case 'G':
+ case 'k':
+ case 'K':
+ if (end - p >= 2) {
+ if (p[1] == 's' || p[1] == 'S') {
+ /* Milliseconds */
+ if (!need_double) {
+ need_double = true;
+ dv = lv;
+ }
+ is_date = true;
+ if (p[0] == 'm' || p[0] == 'M') {
+ dv /= 1000.;
+ }
+ else {
+ dv *= ucl_lex_num_multiplier (*p, false);
+ }
+ p += 2;
+ goto set_obj;
+ }
+ else if (number_bytes || (p[1] == 'b' || p[1] == 'B')) {
+ /* Bytes */
+ if (need_double) {
+ need_double = false;
+ lv = dv;
+ }
+ lv *= ucl_lex_num_multiplier (*p, true);
+ p += 2;
+ goto set_obj;
+ }
+ else if (ucl_lex_is_atom_end (p[1])) {
+ if (need_double) {
+ dv *= ucl_lex_num_multiplier (*p, false);
+ }
+ else {
+ lv *= ucl_lex_num_multiplier (*p, number_bytes);
+ }
+ p ++;
+ goto set_obj;
+ }
+ else if (end - p >= 3) {
+ if (tolower (p[0]) == 'm' &&
+ tolower (p[1]) == 'i' &&
+ tolower (p[2]) == 'n') {
+ /* Minutes */
+ if (!need_double) {
+ need_double = true;
+ dv = lv;
+ }
+ is_date = true;
+ dv *= 60.;
+ p += 3;
+ goto set_obj;
+ }
+ }
+ }
+ else {
+ if (need_double) {
+ dv *= ucl_lex_num_multiplier (*p, false);
+ }
+ else {
+ lv *= ucl_lex_num_multiplier (*p, number_bytes);
+ }
+ p ++;
+ goto set_obj;
+ }
+ break;
+ case 'S':
+ case 's':
+ if (p == end - 1 || ucl_lex_is_atom_end (p[1])) {
+ if (!need_double) {
+ need_double = true;
+ dv = lv;
+ }
+ p ++;
+ is_date = true;
+ goto set_obj;
+ }
+ break;
+ case 'h':
+ case 'H':
+ case 'd':
+ case 'D':
+ case 'w':
+ case 'W':
+ case 'Y':
+ case 'y':
+ if (p == end - 1 || ucl_lex_is_atom_end (p[1])) {
+ if (!need_double) {
+ need_double = true;
+ dv = lv;
+ }
+ is_date = true;
+ dv *= ucl_lex_time_multiplier (*p);
+ p ++;
+ goto set_obj;
+ }
+ break;
+ }
+ }
+
+ *pos = c;
+ return EINVAL;
+
+ set_obj:
+ if (allow_double && (need_double || is_date)) {
+ if (!is_date) {
+ obj->type = UCL_FLOAT;
+ }
+ else {
+ obj->type = UCL_TIME;
+ }
+ obj->value.dv = is_neg ? (-dv) : dv;
+ }
+ else {
+ obj->type = UCL_INT;
+ obj->value.iv = is_neg ? (-lv) : lv;
+ }
+ *pos = p;
+ return 0;
+}
+
+/**
+ * Parse possible number
+ * @param parser
+ * @param chunk
+ * @return true if a number has been parsed
+ */
+static bool
+ucl_lex_number (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, ucl_object_t *obj)
+{
+ const unsigned char *pos;
+ int ret;
+
+ ret = ucl_maybe_parse_number (obj, chunk->pos, chunk->end, (const char **)&pos, true, false);
+
+ if (ret == 0) {
+ chunk->remain -= pos - chunk->pos;
+ chunk->column += pos - chunk->pos;
+ chunk->pos = pos;
+ return true;
+ }
+ else if (ret == ERANGE) {
+ ucl_set_err (chunk, ERANGE, "numeric value out of range", &parser->err);
+ }
+
+ return false;
+}
+
+/**
+ * Parse quoted string with possible escapes
+ * @param parser
+ * @param chunk
+ * @return true if a string has been parsed
+ */
+static bool
+ucl_lex_json_string (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, bool *need_unescape, bool *ucl_escape, bool *var_expand)
+{
+ const unsigned char *p = chunk->pos;
+ unsigned char c;
+ int i;
+
+ while (p < chunk->end) {
+ c = *p;
+ if (c < 0x1F) {
+ /* Unmasked control character */
+ if (c == '\n') {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unexpected newline", &parser->err);
+ }
+ else {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unexpected control character", &parser->err);
+ }
+ return false;
+ }
+ else if (c == '\\') {
+ ucl_chunk_skipc (chunk, p);
+ c = *p;
+ if (p >= chunk->end) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", &parser->err);
+ return false;
+ }
+ else if (ucl_test_character (c, UCL_CHARACTER_ESCAPE)) {
+ if (c == 'u') {
+ ucl_chunk_skipc (chunk, p);
+ for (i = 0; i < 4 && p < chunk->end; i ++) {
+ if (!isxdigit (*p)) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "invalid utf escape", &parser->err);
+ return false;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ if (p >= chunk->end) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", &parser->err);
+ return false;
+ }
+ }
+ else {
+ ucl_chunk_skipc (chunk, p);
+ }
+ }
+ *need_unescape = true;
+ *ucl_escape = true;
+ continue;
+ }
+ else if (c == '"') {
+ ucl_chunk_skipc (chunk, p);
+ return true;
+ }
+ else if (ucl_test_character (c, UCL_CHARACTER_UCL_UNSAFE)) {
+ *ucl_escape = true;
+ }
+ else if (c == '$') {
+ *var_expand = true;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+
+ ucl_set_err (chunk, UCL_ESYNTAX, "no quote at the end of json string", &parser->err);
+ return false;
+}
+
+/**
+ * Parse a key in an object
+ * @param parser
+ * @param chunk
+ * @return true if a key has been parsed
+ */
+static bool
+ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_key, bool *end_of_object)
+{
+ const unsigned char *p, *c = NULL, *end, *t;
+ const char *key = NULL;
+ bool got_quote = false, got_eq = false, got_semicolon = false,
+ need_unescape = false, ucl_escape = false, var_expand = false,
+ got_content = false, got_sep = false;
+ ucl_object_t *nobj, *tobj;
+ ucl_hash_t *container;
+ ssize_t keylen;
+
+ p = chunk->pos;
+
+ if (*p == '.') {
+ /* It is macro actually */
+ ucl_chunk_skipc (chunk, p);
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_MACRO_NAME;
+ return true;
+ }
+ while (p < chunk->end) {
+ /*
+ * A key must start with alpha, number, '/' or '_' and end with space character
+ */
+ if (c == NULL) {
+ if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ p = chunk->pos;
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_KEY_START)) {
+ /* The first symbol */
+ c = p;
+ ucl_chunk_skipc (chunk, p);
+ got_content = true;
+ }
+ else if (*p == '"') {
+ /* JSON style key */
+ c = p + 1;
+ got_quote = true;
+ got_content = true;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (*p == '}') {
+ /* We have actually end of an object */
+ *end_of_object = true;
+ return true;
+ }
+ else if (*p == '.') {
+ ucl_chunk_skipc (chunk, p);
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_MACRO_NAME;
+ return true;
+ }
+ else {
+ /* Invalid identifier */
+ ucl_set_err (chunk, UCL_ESYNTAX, "key must begin with a letter", &parser->err);
+ return false;
+ }
+ }
+ else {
+ /* Parse the body of a key */
+ if (!got_quote) {
+ if (ucl_test_character (*p, UCL_CHARACTER_KEY)) {
+ got_content = true;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_KEY_SEP)) {
+ end = p;
+ break;
+ }
+ else {
+ ucl_set_err (chunk, UCL_ESYNTAX, "invalid character in a key", &parser->err);
+ return false;
+ }
+ }
+ else {
+ /* We need to parse json like quoted string */
+ if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) {
+ return false;
+ }
+ /* Always escape keys obtained via json */
+ end = chunk->pos - 1;
+ p = chunk->pos;
+ break;
+ }
+ }
+ }
+
+ if (p >= chunk->end && got_content) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err);
+ return false;
+ }
+ else if (!got_content) {
+ return true;
+ }
+ *end_of_object = false;
+ /* We are now at the end of the key, need to parse the rest */
+ while (p < chunk->end) {
+ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (*p == '=') {
+ if (!got_eq && !got_semicolon) {
+ ucl_chunk_skipc (chunk, p);
+ got_eq = true;
+ }
+ else {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unexpected '=' character", &parser->err);
+ return false;
+ }
+ }
+ else if (*p == ':') {
+ if (!got_eq && !got_semicolon) {
+ ucl_chunk_skipc (chunk, p);
+ got_semicolon = true;
+ }
+ else {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unexpected ':' character", &parser->err);
+ return false;
+ }
+ }
+ else if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
+ /* Check for comment */
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ p = chunk->pos;
+ }
+ else {
+ /* Start value */
+ break;
+ }
+ }
+
+ if (p >= chunk->end && got_content) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err);
+ return false;
+ }
+
+ got_sep = got_semicolon || got_eq;
+
+ if (!got_sep) {
+ /*
+ * Maybe we have more keys nested, so search for termination character.
+ * Possible choices:
+ * 1) key1 key2 ... keyN [:=] value <- we treat that as error
+ * 2) key1 ... keyN {} or [] <- we treat that as nested objects
+ * 3) key1 value[;,\n] <- we treat that as linear object
+ */
+ t = p;
+ *next_key = false;
+ while (ucl_test_character (*t, UCL_CHARACTER_WHITESPACE)) {
+ t ++;
+ }
+ /* Check first non-space character after a key */
+ if (*t != '{' && *t != '[') {
+ while (t < chunk->end) {
+ if (*t == ',' || *t == ';' || *t == '\n' || *t == '\r') {
+ break;
+ }
+ else if (*t == '{' || *t == '[') {
+ *next_key = true;
+ break;
+ }
+ t ++;
+ }
+ }
+ }
+
+ /* Create a new object */
+ nobj = ucl_object_new ();
+ keylen = ucl_copy_or_store_ptr (parser, c, &nobj->trash_stack[UCL_TRASH_KEY],
+ &key, end - c, need_unescape, parser->flags & UCL_PARSER_KEY_LOWERCASE, false);
+ if (keylen == -1) {
+ ucl_object_free(nobj);
+ return false;
+ }
+ else if (keylen == 0) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "empty keys are not allowed", &parser->err);
+ ucl_object_free(nobj);
+ return false;
+ }
+
+ container = parser->stack->obj->value.ov;
+ nobj->key = key;
+ nobj->keylen = keylen;
+ tobj = ucl_hash_search_obj (container, nobj);
+ if (tobj == NULL) {
+ container = ucl_hash_insert_object (container, nobj);
+ nobj->prev = nobj;
+ nobj->next = NULL;
+ parser->stack->obj->len ++;
+ }
+ else {
+ DL_APPEND (tobj, nobj);
+ }
+
+ if (ucl_escape) {
+ nobj->flags |= UCL_OBJECT_NEED_KEY_ESCAPE;
+ }
+ parser->stack->obj->value.ov = container;
+
+ parser->cur_obj = nobj;
+
+ return true;
+}
+
+/**
+ * Parse a cl string
+ * @param parser
+ * @param chunk
+ * @return true if a key has been parsed
+ */
+static bool
+ucl_parse_string_value (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, bool *var_expand, bool *need_unescape)
+{
+ const unsigned char *p;
+ enum {
+ UCL_BRACE_ROUND = 0,
+ UCL_BRACE_SQUARE,
+ UCL_BRACE_FIGURE
+ };
+ int braces[3][2] = {{0, 0}, {0, 0}, {0, 0}};
+
+ p = chunk->pos;
+
+ while (p < chunk->end) {
+
+ /* Skip pairs of figure braces */
+ if (*p == '{') {
+ braces[UCL_BRACE_FIGURE][0] ++;
+ }
+ else if (*p == '}') {
+ braces[UCL_BRACE_FIGURE][1] ++;
+ if (braces[UCL_BRACE_FIGURE][1] <= braces[UCL_BRACE_FIGURE][0]) {
+ /* This is not a termination symbol, continue */
+ ucl_chunk_skipc (chunk, p);
+ continue;
+ }
+ }
+ /* Skip pairs of square braces */
+ else if (*p == '[') {
+ braces[UCL_BRACE_SQUARE][0] ++;
+ }
+ else if (*p == ']') {
+ braces[UCL_BRACE_SQUARE][1] ++;
+ if (braces[UCL_BRACE_SQUARE][1] <= braces[UCL_BRACE_SQUARE][0]) {
+ /* This is not a termination symbol, continue */
+ ucl_chunk_skipc (chunk, p);
+ continue;
+ }
+ }
+ else if (*p == '$') {
+ *var_expand = true;
+ }
+ else if (*p == '\\') {
+ *need_unescape = true;
+ ucl_chunk_skipc (chunk, p);
+ if (p < chunk->end) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ continue;
+ }
+
+ if (ucl_lex_is_atom_end (*p) || (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1]))) {
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+
+ if (p >= chunk->end) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unfinished value", &parser->err);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse multiline string ending with \n{term}\n
+ * @param parser
+ * @param chunk
+ * @param term
+ * @param term_len
+ * @return size of multiline string or 0 in case of error
+ */
+static int
+ucl_parse_multiline_string (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, const unsigned char *term,
+ int term_len, unsigned char const **beg,
+ bool *var_expand)
+{
+ const unsigned char *p, *c;
+ bool newline = false;
+ int len = 0;
+
+ p = chunk->pos;
+
+ c = p;
+
+ while (p < chunk->end) {
+ if (newline) {
+ if (chunk->end - p < term_len) {
+ return 0;
+ }
+ else if (memcmp (p, term, term_len) == 0 && (p[term_len] == '\n' || p[term_len] == '\r')) {
+ len = p - c;
+ chunk->remain -= term_len;
+ chunk->pos = p + term_len;
+ chunk->column = term_len;
+ *beg = c;
+ break;
+ }
+ }
+ if (*p == '\n') {
+ newline = true;
+ }
+ else {
+ if (*p == '$') {
+ *var_expand = true;
+ }
+ newline = false;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+
+ return len;
+}
+
+/**
+ * Handle value data
+ * @param parser
+ * @param chunk
+ * @return
+ */
+static bool
+ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
+{
+ const unsigned char *p, *c;
+ ucl_object_t *obj = NULL, *t;
+ unsigned int stripped_spaces;
+ int str_len;
+ bool need_unescape = false, ucl_escape = false, var_expand = false;
+
+ p = chunk->pos;
+
+ while (p < chunk->end) {
+ if (obj == NULL) {
+ if (parser->stack->obj->type == UCL_ARRAY) {
+ /* Object must be allocated */
+ obj = ucl_object_new ();
+ t = parser->stack->obj->value.av;
+ DL_APPEND (t, obj);
+ parser->cur_obj = obj;
+ parser->stack->obj->value.av = t;
+ parser->stack->obj->len ++;
+ }
+ else {
+ /* Object has been already allocated */
+ obj = parser->cur_obj;
+ }
+ }
+ c = p;
+ switch (*p) {
+ case '"':
+ ucl_chunk_skipc (chunk, p);
+ if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) {
+ return false;
+ }
+ str_len = chunk->pos - c - 2;
+ obj->type = UCL_STRING;
+ if ((str_len = ucl_copy_or_store_ptr (parser, c + 1, &obj->trash_stack[UCL_TRASH_VALUE],
+ &obj->value.sv, str_len, need_unescape, false, var_expand)) == -1) {
+ return false;
+ }
+ obj->len = str_len;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ p = chunk->pos;
+ return true;
+ break;
+ case '{':
+ /* We have a new object */
+ obj = ucl_add_parser_stack (obj, parser, false, parser->stack->level);
+
+ ucl_chunk_skipc (chunk, p);
+ return true;
+ break;
+ case '[':
+ /* We have a new array */
+ obj = ucl_add_parser_stack (obj, parser, true, parser->stack->level);
+
+ ucl_chunk_skipc (chunk, p);
+ return true;
+ break;
+ case '<':
+ /* We have something like multiline value, which must be <<[A-Z]+\n */
+ if (chunk->end - p > 3) {
+ if (memcmp (p, "<<", 2) == 0) {
+ p += 2;
+ /* We allow only uppercase characters in multiline definitions */
+ while (p < chunk->end && *p >= 'A' && *p <= 'Z') {
+ p ++;
+ }
+ if (*p =='\n') {
+ /* Set chunk positions and start multiline parsing */
+ c += 2;
+ chunk->remain -= p - c;
+ chunk->pos = p + 1;
+ chunk->column = 0;
+ chunk->line ++;
+ if ((str_len = ucl_parse_multiline_string (parser, chunk, c,
+ p - c, &c, &var_expand)) == 0) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unterminated multiline value", &parser->err);
+ return false;
+ }
+ obj->type = UCL_STRING;
+ if ((str_len = ucl_copy_or_store_ptr (parser, c, &obj->trash_stack[UCL_TRASH_VALUE],
+ &obj->value.sv, str_len - 1, false, false, var_expand)) == -1) {
+ return false;
+ }
+ obj->len = str_len;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ return true;
+ }
+ }
+ }
+ /* Fallback to ordinary strings */
+ default:
+ /* Skip any spaces and comments */
+ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) ||
+ (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1]))) {
+ while (p < chunk->end && ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ p = chunk->pos;
+ continue;
+ }
+ /* Parse atom */
+ if (ucl_test_character (*p, UCL_CHARACTER_VALUE_DIGIT_START)) {
+ if (!ucl_lex_number (parser, chunk, obj)) {
+ if (parser->state == UCL_STATE_ERROR) {
+ return false;
+ }
+ }
+ else {
+ parser->state = UCL_STATE_AFTER_VALUE;
+ return true;
+ }
+ /* Fallback to normal string */
+ }
+
+ if (!ucl_parse_string_value (parser, chunk, &var_expand, &need_unescape)) {
+ return false;
+ }
+ /* Cut trailing spaces */
+ stripped_spaces = 0;
+ while (ucl_test_character (*(chunk->pos - 1 - stripped_spaces),
+ UCL_CHARACTER_WHITESPACE)) {
+ stripped_spaces ++;
+ }
+ str_len = chunk->pos - c - stripped_spaces;
+ if (str_len <= 0) {
+ ucl_set_err (chunk, 0, "string value must not be empty", &parser->err);
+ return false;
+ }
+ else if (str_len == 4 && memcmp (c, "null", 4) == 0) {
+ obj->len = 0;
+ obj->type = UCL_NULL;
+ }
+ else if (!ucl_maybe_parse_boolean (obj, c, str_len)) {
+ obj->type = UCL_STRING;
+ if ((str_len = ucl_copy_or_store_ptr (parser, c, &obj->trash_stack[UCL_TRASH_VALUE],
+ &obj->value.sv, str_len, need_unescape,
+ false, var_expand)) == -1) {
+ return false;
+ }
+ obj->len = str_len;
+ }
+ parser->state = UCL_STATE_AFTER_VALUE;
+ p = chunk->pos;
+
+ return true;
+ break;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Handle after value data
+ * @param parser
+ * @param chunk
+ * @return
+ */
+static bool
+ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
+{
+ const unsigned char *p;
+ bool got_sep = false;
+ struct ucl_stack *st;
+
+ p = chunk->pos;
+
+ while (p < chunk->end) {
+ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE)) {
+ /* Skip whitespaces */
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
+ /* Skip comment */
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ /* Treat comment as a separator */
+ got_sep = true;
+ p = chunk->pos;
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_VALUE_END)) {
+ if (*p == '}' || *p == ']') {
+ if (parser->stack == NULL) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "end of array or object detected without corresponding start", &parser->err);
+ return false;
+ }
+ if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) ||
+ (*p == ']' && parser->stack->obj->type == UCL_ARRAY)) {
+
+ /* Pop all nested objects from a stack */
+ st = parser->stack;
+ parser->stack = st->next;
+ UCL_FREE (sizeof (struct ucl_stack), st);
+
+ while (parser->stack != NULL) {
+ st = parser->stack;
+ if (st->next == NULL || st->next->level == st->level) {
+ break;
+ }
+ parser->stack = st->next;
+ UCL_FREE (sizeof (struct ucl_stack), st);
+ }
+ }
+ else {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unexpected terminating symbol detected", &parser->err);
+ return false;
+ }
+
+ if (parser->stack == NULL) {
+ /* Ignore everything after a top object */
+ return true;
+ }
+ else {
+ ucl_chunk_skipc (chunk, p);
+ }
+ got_sep = true;
+ }
+ else {
+ /* Got a separator */
+ got_sep = true;
+ ucl_chunk_skipc (chunk, p);
+ }
+ }
+ else {
+ /* Anything else */
+ if (!got_sep) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "delimiter is missing", &parser->err);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Handle macro data
+ * @param parser
+ * @param chunk
+ * @return
+ */
+static bool
+ucl_parse_macro_value (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, struct ucl_macro *macro,
+ unsigned char const **macro_start, size_t *macro_len)
+{
+ const unsigned char *p, *c;
+ bool need_unescape = false, ucl_escape = false, var_expand = false;
+
+ p = chunk->pos;
+
+ switch (*p) {
+ case '"':
+ /* We have macro value encoded in quotes */
+ c = p;
+ ucl_chunk_skipc (chunk, p);
+ if (!ucl_lex_json_string (parser, chunk, &need_unescape, &ucl_escape, &var_expand)) {
+ return false;
+ }
+
+ *macro_start = c + 1;
+ *macro_len = chunk->pos - c - 2;
+ p = chunk->pos;
+ break;
+ case '{':
+ /* We got a multiline macro body */
+ ucl_chunk_skipc (chunk, p);
+ /* Skip spaces at the beginning */
+ while (p < chunk->end) {
+ if (ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ else {
+ break;
+ }
+ }
+ c = p;
+ while (p < chunk->end) {
+ if (*p == '}') {
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ *macro_start = c;
+ *macro_len = p - c;
+ ucl_chunk_skipc (chunk, p);
+ break;
+ default:
+ /* Macro is not enclosed in quotes or braces */
+ c = p;
+ while (p < chunk->end) {
+ if (ucl_lex_is_atom_end (*p)) {
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ *macro_start = c;
+ *macro_len = p - c;
+ break;
+ }
+
+ /* We are at the end of a macro */
+ /* Skip ';' and space characters and return to previous state */
+ while (p < chunk->end) {
+ if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) && *p != ';') {
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ return true;
+}
+
+/**
+ * Handle the main states of rcl parser
+ * @param parser parser structure
+ * @param data the pointer to the beginning of a chunk
+ * @param len the length of a chunk
+ * @return true if chunk has been parsed and false in case of error
+ */
+static bool
+ucl_state_machine (struct ucl_parser *parser)
+{
+ ucl_object_t *obj;
+ struct ucl_chunk *chunk = parser->chunks;
+ const unsigned char *p, *c = NULL, *macro_start = NULL;
+ unsigned char *macro_escaped;
+ size_t macro_len = 0;
+ struct ucl_macro *macro = NULL;
+ bool next_key = false, end_of_object = false;
+
+ if (parser->top_obj == NULL) {
+ if (*chunk->pos == '[') {
+ obj = ucl_add_parser_stack (NULL, parser, true, 0);
+ }
+ else {
+ obj = ucl_add_parser_stack (NULL, parser, false, 0);
+ }
+ parser->top_obj = obj;
+ parser->cur_obj = obj;
+ parser->state = UCL_STATE_INIT;
+ }
+
+ p = chunk->pos;
+ while (chunk->pos < chunk->end) {
+ switch (parser->state) {
+ case UCL_STATE_INIT:
+ /*
+ * At the init state we can either go to the parse array or object
+ * if we got [ or { correspondingly or can just treat new data as
+ * a key of newly created object
+ */
+ obj = parser->cur_obj;
+ if (!ucl_skip_comments (parser)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ else {
+ p = chunk->pos;
+ if (*p == '[') {
+ parser->state = UCL_STATE_VALUE;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else {
+ parser->state = UCL_STATE_KEY;
+ if (*p == '{') {
+ ucl_chunk_skipc (chunk, p);
+ }
+ }
+ }
+ break;
+ case UCL_STATE_KEY:
+ /* Skip any spaces */
+ while (p < chunk->end && ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ if (*p == '}') {
+ /* We have the end of an object */
+ parser->state = UCL_STATE_AFTER_VALUE;
+ continue;
+ }
+ if (parser->stack == NULL) {
+ /* No objects are on stack, but we want to parse a key */
+ ucl_set_err (chunk, UCL_ESYNTAX, "top object is finished but the parser "
+ "expects a key", &parser->err);
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ if (!ucl_parse_key (parser, chunk, &next_key, &end_of_object)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ if (end_of_object) {
+ p = chunk->pos;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ continue;
+ }
+ else if (parser->state != UCL_STATE_MACRO_NAME) {
+ if (next_key && parser->stack->obj->type == UCL_OBJECT) {
+ /* Parse more keys and nest objects accordingly */
+ obj = ucl_add_parser_stack (parser->cur_obj, parser, false, parser->stack->level + 1);
+ }
+ else {
+ parser->state = UCL_STATE_VALUE;
+ }
+ }
+ else {
+ c = chunk->pos;
+ }
+ p = chunk->pos;
+ break;
+ case UCL_STATE_VALUE:
+ /* We need to check what we do have */
+ if (!ucl_parse_value (parser, chunk)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ /* State is set in ucl_parse_value call */
+ p = chunk->pos;
+ break;
+ case UCL_STATE_AFTER_VALUE:
+ if (!ucl_parse_after_value (parser, chunk)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ if (parser->stack != NULL) {
+ if (parser->stack->obj->type == UCL_OBJECT) {
+ parser->state = UCL_STATE_KEY;
+ }
+ else {
+ /* Array */
+ parser->state = UCL_STATE_VALUE;
+ }
+ }
+ else {
+ /* Skip everything at the end */
+ return true;
+ }
+ p = chunk->pos;
+ break;
+ case UCL_STATE_MACRO_NAME:
+ if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (p - c > 0) {
+ /* We got macro name */
+ macro_len = (size_t)(p - c);
+ HASH_FIND (hh, parser->macroes, c, macro_len, macro);
+ if (macro == NULL) {
+ ucl_create_err (&parser->err, "error on line %d at column %d: "
+ "unknown macro: '%.*s', character: '%c'",
+ chunk->line, chunk->column, (int)(p - c), c, *chunk->pos);
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ /* Now we need to skip all spaces */
+ while (p < chunk->end) {
+ if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
+ /* Skip comment */
+ if (!ucl_skip_comments (parser)) {
+ return false;
+ }
+ p = chunk->pos;
+ }
+ break;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ parser->state = UCL_STATE_MACRO;
+ }
+ break;
+ case UCL_STATE_MACRO:
+ if (!ucl_parse_macro_value (parser, chunk, macro,
+ &macro_start, &macro_len)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ macro_len = ucl_expand_variable (parser, &macro_escaped, macro_start, macro_len);
+ parser->state = parser->prev_state;
+ if (macro_escaped == NULL) {
+ if (!macro->handler (macro_start, macro_len, macro->ud)) {
+ return false;
+ }
+ }
+ else {
+ if (!macro->handler (macro_escaped, macro_len, macro->ud)) {
+ UCL_FREE (macro_len + 1, macro_escaped);
+ return false;
+ }
+ UCL_FREE (macro_len + 1, macro_escaped);
+ }
+ p = chunk->pos;
+ break;
+ default:
+ /* TODO: add all states */
+ ucl_set_err (chunk, UCL_EINTERNAL, "internal error: parser is in an unknown state", &parser->err);
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+struct ucl_parser*
+ucl_parser_new (int flags)
+{
+ struct ucl_parser *new;
+
+ new = UCL_ALLOC (sizeof (struct ucl_parser));
+ memset (new, 0, sizeof (struct ucl_parser));
+
+ ucl_parser_register_macro (new, "include", ucl_include_handler, new);
+ ucl_parser_register_macro (new, "try_include", ucl_try_include_handler, new);
+ ucl_parser_register_macro (new, "includes", ucl_includes_handler, new);
+
+ new->flags = flags;
+
+ /* Initial assumption about filevars */
+ ucl_parser_set_filevars (new, NULL, false);
+
+ return new;
+}
+
+
+void
+ucl_parser_register_macro (struct ucl_parser *parser, const char *macro,
+ ucl_macro_handler handler, void* ud)
+{
+ struct ucl_macro *new;
+
+ new = UCL_ALLOC (sizeof (struct ucl_macro));
+ memset (new, 0, sizeof (struct ucl_macro));
+ new->handler = handler;
+ new->name = strdup (macro);
+ new->ud = ud;
+ HASH_ADD_KEYPTR (hh, parser->macroes, new->name, strlen (new->name), new);
+}
+
+void
+ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
+ const char *value)
+{
+ struct ucl_variable *new = NULL, *cur;
+
+ if (var == NULL) {
+ return;
+ }
+
+ /* Find whether a variable already exists */
+ LL_FOREACH (parser->variables, cur) {
+ if (strcmp (cur->var, var) == 0) {
+ new = cur;
+ break;
+ }
+ }
+
+ if (value == NULL) {
+
+ if (new != NULL) {
+ /* Remove variable */
+ LL_DELETE (parser->variables, new);
+ free (new->var);
+ free (new->value);
+ UCL_FREE (sizeof (struct ucl_variable), new);
+ }
+ else {
+ /* Do nothing */
+ return;
+ }
+ }
+ else {
+ if (new == NULL) {
+ new = UCL_ALLOC (sizeof (struct ucl_variable));
+ memset (new, 0, sizeof (struct ucl_variable));
+ new->var = strdup (var);
+ new->var_len = strlen (var);
+ new->value = strdup (value);
+ new->value_len = strlen (value);
+
+ LL_PREPEND (parser->variables, new);
+ }
+ else {
+ free (new->value);
+ new->value = strdup (value);
+ new->value_len = strlen (value);
+ }
+ }
+}
+
+bool
+ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
+ size_t len)
+{
+ struct ucl_chunk *chunk;
+
+ if (parser->state != UCL_STATE_ERROR) {
+ chunk = UCL_ALLOC (sizeof (struct ucl_chunk));
+ chunk->begin = data;
+ chunk->remain = len;
+ chunk->pos = chunk->begin;
+ chunk->end = chunk->begin + len;
+ chunk->line = 1;
+ chunk->column = 0;
+ LL_PREPEND (parser->chunks, chunk);
+ parser->recursion ++;
+ if (parser->recursion > UCL_MAX_RECURSION) {
+ ucl_create_err (&parser->err, "maximum include nesting limit is reached: %d",
+ parser->recursion);
+ return false;
+ }
+ return ucl_state_machine (parser);
+ }
+
+ ucl_create_err (&parser->err, "a parser is in an invalid state");
+
+ return false;
+}
diff --git a/src/ucl_util.c b/src/ucl_util.c
new file mode 100644
index 000000000000..6ab1d38e7252
--- /dev/null
+++ b/src/ucl_util.c
@@ -0,0 +1,1157 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "ucl_chartable.h"
+
+#include <libgen.h> /* For dirname */
+
+#ifdef HAVE_OPENSSL
+#include <openssl/err.h>
+#include <openssl/sha.h>
+#include <openssl/rsa.h>
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+#endif
+
+/**
+ * @file rcl_util.c
+ * Utilities for rcl parsing
+ */
+
+
+static void
+ucl_object_free_internal (ucl_object_t *obj, bool allow_rec)
+{
+ ucl_object_t *sub, *tmp;
+
+ while (obj != NULL) {
+ if (obj->trash_stack[UCL_TRASH_KEY] != NULL) {
+ UCL_FREE (obj->hh.keylen, obj->trash_stack[UCL_TRASH_KEY]);
+ }
+ if (obj->trash_stack[UCL_TRASH_VALUE] != NULL) {
+ UCL_FREE (obj->len, obj->trash_stack[UCL_TRASH_VALUE]);
+ }
+
+ if (obj->type == UCL_ARRAY) {
+ sub = obj->value.av;
+ while (sub != NULL) {
+ tmp = sub->next;
+ ucl_object_free_internal (sub, false);
+ sub = tmp;
+ }
+ }
+ else if (obj->type == UCL_OBJECT) {
+ if (obj->value.ov != NULL) {
+ ucl_hash_destroy (obj->value.ov, (ucl_hash_free_func *)ucl_object_unref);
+ }
+ }
+ tmp = obj->next;
+ UCL_FREE (sizeof (ucl_object_t), obj);
+ obj = tmp;
+
+ if (!allow_rec) {
+ break;
+ }
+ }
+}
+
+void
+ucl_object_free (ucl_object_t *obj)
+{
+ ucl_object_free_internal (obj, true);
+}
+
+size_t
+ucl_unescape_json_string (char *str, size_t len)
+{
+ char *t = str, *h = str;
+ int i, uval;
+
+ /* t is target (tortoise), h is source (hare) */
+
+ while (len) {
+ if (*h == '\\') {
+ h ++;
+ switch (*h) {
+ case 'n':
+ *t++ = '\n';
+ break;
+ case 'r':
+ *t++ = '\r';
+ break;
+ case 'b':
+ *t++ = '\b';
+ break;
+ case 't':
+ *t++ = '\t';
+ break;
+ case 'f':
+ *t++ = '\f';
+ break;
+ case '\\':
+ *t++ = '\\';
+ break;
+ case '"':
+ *t++ = '"';
+ break;
+ case 'u':
+ /* Unicode escape */
+ uval = 0;
+ for (i = 0; i < 4; i++) {
+ uval <<= 4;
+ if (isdigit (h[i])) {
+ uval += h[i] - '0';
+ }
+ else if (h[i] >= 'a' && h[i] <= 'f') {
+ uval += h[i] - 'a' + 10;
+ }
+ else if (h[i] >= 'A' && h[i] <= 'F') {
+ uval += h[i] - 'A' + 10;
+ }
+ }
+ h += 3;
+ len -= 3;
+ /* Encode */
+ if(uval < 0x80) {
+ t[0] = (char)uval;
+ t ++;
+ }
+ else if(uval < 0x800) {
+ t[0] = 0xC0 + ((uval & 0x7C0) >> 6);
+ t[1] = 0x80 + ((uval & 0x03F));
+ t += 2;
+ }
+ else if(uval < 0x10000) {
+ t[0] = 0xE0 + ((uval & 0xF000) >> 12);
+ t[1] = 0x80 + ((uval & 0x0FC0) >> 6);
+ t[2] = 0x80 + ((uval & 0x003F));
+ t += 3;
+ }
+ else if(uval <= 0x10FFFF) {
+ t[0] = 0xF0 + ((uval & 0x1C0000) >> 18);
+ t[1] = 0x80 + ((uval & 0x03F000) >> 12);
+ t[2] = 0x80 + ((uval & 0x000FC0) >> 6);
+ t[3] = 0x80 + ((uval & 0x00003F));
+ t += 4;
+ }
+ else {
+ *t++ = '?';
+ }
+ break;
+ default:
+ *t++ = *h;
+ break;
+ }
+ h ++;
+ len --;
+ }
+ else {
+ *t++ = *h++;
+ }
+ len --;
+ }
+ *t = '\0';
+
+ return (t - str);
+}
+
+char *
+ucl_copy_key_trash (ucl_object_t *obj)
+{
+ if (obj->trash_stack[UCL_TRASH_KEY] == NULL && obj->key != NULL) {
+ obj->trash_stack[UCL_TRASH_KEY] = malloc (obj->keylen + 1);
+ if (obj->trash_stack[UCL_TRASH_KEY] != NULL) {
+ memcpy (obj->trash_stack[UCL_TRASH_KEY], obj->key, obj->keylen);
+ obj->trash_stack[UCL_TRASH_KEY][obj->keylen] = '\0';
+ }
+ obj->key = obj->trash_stack[UCL_TRASH_KEY];
+ obj->flags |= UCL_OBJECT_ALLOCATED_KEY;
+ }
+
+ return obj->trash_stack[UCL_TRASH_KEY];
+}
+
+char *
+ucl_copy_value_trash (ucl_object_t *obj)
+{
+ if (obj->trash_stack[UCL_TRASH_VALUE] == NULL) {
+ if (obj->type == UCL_STRING) {
+ /* Special case for strings */
+ obj->trash_stack[UCL_TRASH_VALUE] = malloc (obj->len + 1);
+ if (obj->trash_stack[UCL_TRASH_VALUE] != NULL) {
+ memcpy (obj->trash_stack[UCL_TRASH_VALUE], obj->value.sv, obj->len);
+ obj->trash_stack[UCL_TRASH_VALUE][obj->len] = '\0';
+ obj->value.sv = obj->trash_stack[UCL_TRASH_VALUE];
+ }
+ }
+ else {
+ /* Just emit value in json notation */
+ obj->trash_stack[UCL_TRASH_VALUE] = ucl_object_emit_single_json (obj);
+ obj->len = strlen (obj->trash_stack[UCL_TRASH_VALUE]);
+ }
+ obj->flags |= UCL_OBJECT_ALLOCATED_VALUE;
+ }
+ return obj->trash_stack[UCL_TRASH_VALUE];
+}
+
+ucl_object_t*
+ucl_parser_get_object (struct ucl_parser *parser)
+{
+ if (parser->state != UCL_STATE_ERROR && parser->top_obj != NULL) {
+ return ucl_object_ref (parser->top_obj);
+ }
+
+ return NULL;
+}
+
+void
+ucl_parser_free (struct ucl_parser *parser)
+{
+ struct ucl_stack *stack, *stmp;
+ struct ucl_macro *macro, *mtmp;
+ struct ucl_chunk *chunk, *ctmp;
+ struct ucl_pubkey *key, *ktmp;
+ struct ucl_variable *var, *vtmp;
+
+ if (parser->top_obj != NULL) {
+ ucl_object_unref (parser->top_obj);
+ }
+
+ LL_FOREACH_SAFE (parser->stack, stack, stmp) {
+ free (stack);
+ }
+ HASH_ITER (hh, parser->macroes, macro, mtmp) {
+ free (macro->name);
+ HASH_DEL (parser->macroes, macro);
+ UCL_FREE (sizeof (struct ucl_macro), macro);
+ }
+ LL_FOREACH_SAFE (parser->chunks, chunk, ctmp) {
+ UCL_FREE (sizeof (struct ucl_chunk), chunk);
+ }
+ LL_FOREACH_SAFE (parser->keys, key, ktmp) {
+ UCL_FREE (sizeof (struct ucl_pubkey), key);
+ }
+ LL_FOREACH_SAFE (parser->variables, var, vtmp) {
+ free (var->value);
+ free (var->var);
+ UCL_FREE (sizeof (struct ucl_variable), var);
+ }
+
+ if (parser->err != NULL) {
+ utstring_free(parser->err);
+ }
+
+ UCL_FREE (sizeof (struct ucl_parser), parser);
+}
+
+const char *
+ucl_parser_get_error(struct ucl_parser *parser)
+{
+ if (parser->err == NULL)
+ return NULL;
+
+ return utstring_body(parser->err);
+}
+
+bool
+ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len)
+{
+#ifndef HAVE_OPENSSL
+ ucl_create_err (&parser->err, "cannot check signatures without openssl");
+ return false;
+#else
+# if (OPENSSL_VERSION_NUMBER < 0x10000000L)
+ ucl_create_err (&parser->err, "cannot check signatures, openssl version is unsupported");
+ return EXIT_FAILURE;
+# else
+ struct ucl_pubkey *nkey;
+ BIO *mem;
+
+ mem = BIO_new_mem_buf ((void *)key, len);
+ nkey = UCL_ALLOC (sizeof (struct ucl_pubkey));
+ nkey->key = PEM_read_bio_PUBKEY (mem, &nkey->key, NULL, NULL);
+ BIO_free (mem);
+ if (nkey->key == NULL) {
+ UCL_FREE (sizeof (struct ucl_pubkey), nkey);
+ ucl_create_err (&parser->err, "%s",
+ ERR_error_string (ERR_get_error (), NULL));
+ return false;
+ }
+ LL_PREPEND (parser->keys, nkey);
+# endif
+#endif
+ return true;
+}
+
+#ifdef CURL_FOUND
+struct ucl_curl_cbdata {
+ unsigned char *buf;
+ size_t buflen;
+};
+
+static size_t
+ucl_curl_write_callback (void* contents, size_t size, size_t nmemb, void* ud)
+{
+ struct ucl_curl_cbdata *cbdata = ud;
+ size_t realsize = size * nmemb;
+
+ cbdata->buf = realloc (cbdata->buf, cbdata->buflen + realsize + 1);
+ if (cbdata->buf == NULL) {
+ return 0;
+ }
+
+ memcpy (&(cbdata->buf[cbdata->buflen]), contents, realsize);
+ cbdata->buflen += realsize;
+ cbdata->buf[cbdata->buflen] = 0;
+
+ return realsize;
+}
+#endif
+
+/**
+ * Fetch a url and save results to the memory buffer
+ * @param url url to fetch
+ * @param len length of url
+ * @param buf target buffer
+ * @param buflen target length
+ * @return
+ */
+static bool
+ucl_fetch_url (const unsigned char *url, unsigned char **buf, size_t *buflen,
+ UT_string **err, bool must_exist)
+{
+
+#ifdef HAVE_FETCH_H
+ struct url *fetch_url;
+ struct url_stat us;
+ FILE *in;
+
+ fetch_url = fetchParseURL (url);
+ if (fetch_url == NULL) {
+ ucl_create_err (err, "invalid URL %s: %s",
+ url, strerror (errno));
+ return false;
+ }
+ if ((in = fetchXGet (fetch_url, &us, "")) == NULL) {
+ if (!must_exist) {
+ ucl_create_err (err, "cannot fetch URL %s: %s",
+ url, strerror (errno));
+ }
+ fetchFreeURL (fetch_url);
+ return false;
+ }
+
+ *buflen = us.size;
+ *buf = malloc (*buflen);
+ if (*buf == NULL) {
+ ucl_create_err (err, "cannot allocate buffer for URL %s: %s",
+ url, strerror (errno));
+ fclose (in);
+ fetchFreeURL (fetch_url);
+ return false;
+ }
+
+ if (fread (*buf, *buflen, 1, in) != 1) {
+ ucl_create_err (err, "cannot read URL %s: %s",
+ url, strerror (errno));
+ fclose (in);
+ fetchFreeURL (fetch_url);
+ return false;
+ }
+
+ fetchFreeURL (fetch_url);
+ return true;
+#elif defined(CURL_FOUND)
+ CURL *curl;
+ int r;
+ struct ucl_curl_cbdata cbdata;
+
+ curl = curl_easy_init ();
+ if (curl == NULL) {
+ ucl_create_err (err, "CURL interface is broken");
+ return false;
+ }
+ if ((r = curl_easy_setopt (curl, CURLOPT_URL, url)) != CURLE_OK) {
+ ucl_create_err (err, "invalid URL %s: %s",
+ url, curl_easy_strerror (r));
+ curl_easy_cleanup (curl);
+ return false;
+ }
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, ucl_curl_write_callback);
+ cbdata.buf = *buf;
+ cbdata.buflen = *buflen;
+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbdata);
+
+ if ((r = curl_easy_perform (curl)) != CURLE_OK) {
+ if (!must_exist) {
+ ucl_create_err (err, "error fetching URL %s: %s",
+ url, curl_easy_strerror (r));
+ }
+ curl_easy_cleanup (curl);
+ if (cbdata.buf) {
+ free (cbdata.buf);
+ }
+ return false;
+ }
+ *buf = cbdata.buf;
+ *buflen = cbdata.buflen;
+
+ return true;
+#else
+ ucl_create_err (err, "URL support is disabled");
+ return false;
+#endif
+}
+
+/**
+ * Fetch a file and save results to the memory buffer
+ * @param filename filename to fetch
+ * @param len length of filename
+ * @param buf target buffer
+ * @param buflen target length
+ * @return
+ */
+static bool
+ucl_fetch_file (const unsigned char *filename, unsigned char **buf, size_t *buflen,
+ UT_string **err, bool must_exist)
+{
+ int fd;
+ struct stat st;
+
+ if (stat (filename, &st) == -1 || !S_ISREG (st.st_mode)) {
+ if (must_exist) {
+ ucl_create_err (err, "cannot stat file %s: %s",
+ filename, strerror (errno));
+ }
+ return false;
+ }
+ if (st.st_size == 0) {
+ /* Do not map empty files */
+ *buf = "";
+ *buflen = 0;
+ }
+ else {
+ if ((fd = open (filename, O_RDONLY)) == -1) {
+ ucl_create_err (err, "cannot open file %s: %s",
+ filename, strerror (errno));
+ return false;
+ }
+ if ((*buf = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ close (fd);
+ ucl_create_err (err, "cannot mmap file %s: %s",
+ filename, strerror (errno));
+ return false;
+ }
+ *buflen = st.st_size;
+ close (fd);
+ }
+
+ return true;
+}
+
+
+#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L)
+static inline bool
+ucl_sig_check (const unsigned char *data, size_t datalen,
+ const unsigned char *sig, size_t siglen, struct ucl_parser *parser)
+{
+ struct ucl_pubkey *key;
+ char dig[EVP_MAX_MD_SIZE];
+ unsigned int diglen;
+ EVP_PKEY_CTX *key_ctx;
+ EVP_MD_CTX *sign_ctx = NULL;
+
+ sign_ctx = EVP_MD_CTX_create ();
+
+ LL_FOREACH (parser->keys, key) {
+ key_ctx = EVP_PKEY_CTX_new (key->key, NULL);
+ if (key_ctx != NULL) {
+ if (EVP_PKEY_verify_init (key_ctx) <= 0) {
+ EVP_PKEY_CTX_free (key_ctx);
+ continue;
+ }
+ if (EVP_PKEY_CTX_set_rsa_padding (key_ctx, RSA_PKCS1_PADDING) <= 0) {
+ EVP_PKEY_CTX_free (key_ctx);
+ continue;
+ }
+ if (EVP_PKEY_CTX_set_signature_md (key_ctx, EVP_sha256 ()) <= 0) {
+ EVP_PKEY_CTX_free (key_ctx);
+ continue;
+ }
+ EVP_DigestInit (sign_ctx, EVP_sha256 ());
+ EVP_DigestUpdate (sign_ctx, data, datalen);
+ EVP_DigestFinal (sign_ctx, dig, &diglen);
+
+ if (EVP_PKEY_verify (key_ctx, sig, siglen, dig, diglen) == 1) {
+ EVP_MD_CTX_destroy (sign_ctx);
+ EVP_PKEY_CTX_free (key_ctx);
+ return true;
+ }
+
+ EVP_PKEY_CTX_free (key_ctx);
+ }
+ }
+
+ EVP_MD_CTX_destroy (sign_ctx);
+
+ return false;
+}
+#endif
+
+/**
+ * Include an url to configuration
+ * @param data
+ * @param len
+ * @param parser
+ * @param err
+ * @return
+ */
+static bool
+ucl_include_url (const unsigned char *data, size_t len,
+ struct ucl_parser *parser, bool check_signature, bool must_exist)
+{
+
+ bool res;
+ unsigned char *buf = NULL;
+ size_t buflen = 0;
+ struct ucl_chunk *chunk;
+ char urlbuf[PATH_MAX];
+ int prev_state;
+
+ snprintf (urlbuf, sizeof (urlbuf), "%.*s", (int)len, data);
+
+ if (!ucl_fetch_url (urlbuf, &buf, &buflen, &parser->err, must_exist)) {
+ return (!must_exist || false);
+ }
+
+ if (check_signature) {
+#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L)
+ unsigned char *sigbuf = NULL;
+ size_t siglen = 0;
+ /* We need to check signature first */
+ snprintf (urlbuf, sizeof (urlbuf), "%.*s.sig", (int)len, data);
+ if (!ucl_fetch_url (urlbuf, &sigbuf, &siglen, &parser->err, true)) {
+ return false;
+ }
+ if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) {
+ ucl_create_err (&parser->err, "cannot verify url %s: %s",
+ urlbuf,
+ ERR_error_string (ERR_get_error (), NULL));
+ if (siglen > 0) {
+ munmap (sigbuf, siglen);
+ }
+ return false;
+ }
+ if (siglen > 0) {
+ munmap (sigbuf, siglen);
+ }
+#endif
+ }
+
+ prev_state = parser->state;
+ parser->state = UCL_STATE_INIT;
+
+ res = ucl_parser_add_chunk (parser, buf, buflen);
+ if (res == true) {
+ /* Remove chunk from the stack */
+ chunk = parser->chunks;
+ if (chunk != NULL) {
+ parser->chunks = chunk->next;
+ UCL_FREE (sizeof (struct ucl_chunk), chunk);
+ }
+ }
+
+ parser->state = prev_state;
+ free (buf);
+
+ return res;
+}
+
+/**
+ * Include a file to configuration
+ * @param data
+ * @param len
+ * @param parser
+ * @param err
+ * @return
+ */
+static bool
+ucl_include_file (const unsigned char *data, size_t len,
+ struct ucl_parser *parser, bool check_signature, bool must_exist)
+{
+ bool res;
+ struct ucl_chunk *chunk;
+ unsigned char *buf = NULL;
+ size_t buflen;
+ char filebuf[PATH_MAX], realbuf[PATH_MAX];
+ int prev_state;
+
+ snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data);
+ if (realpath (filebuf, realbuf) == NULL) {
+ if (!must_exist) {
+ return true;
+ }
+ ucl_create_err (&parser->err, "cannot open file %s: %s",
+ filebuf,
+ strerror (errno));
+ return false;
+ }
+
+ if (!ucl_fetch_file (realbuf, &buf, &buflen, &parser->err, must_exist)) {
+ return (!must_exist || false);
+ }
+
+ if (check_signature) {
+#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L)
+ unsigned char *sigbuf = NULL;
+ size_t siglen = 0;
+ /* We need to check signature first */
+ snprintf (filebuf, sizeof (filebuf), "%s.sig", realbuf);
+ if (!ucl_fetch_file (filebuf, &sigbuf, &siglen, &parser->err, true)) {
+ return false;
+ }
+ if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) {
+ ucl_create_err (&parser->err, "cannot verify file %s: %s",
+ filebuf,
+ ERR_error_string (ERR_get_error (), NULL));
+ if (siglen > 0) {
+ munmap (sigbuf, siglen);
+ }
+ return false;
+ }
+ if (siglen > 0) {
+ munmap (sigbuf, siglen);
+ }
+#endif
+ }
+
+ ucl_parser_set_filevars (parser, realbuf, false);
+
+ prev_state = parser->state;
+ parser->state = UCL_STATE_INIT;
+
+ res = ucl_parser_add_chunk (parser, buf, buflen);
+ if (res == true) {
+ /* Remove chunk from the stack */
+ chunk = parser->chunks;
+ if (chunk != NULL) {
+ parser->chunks = chunk->next;
+ UCL_FREE (sizeof (struct ucl_chunk), chunk);
+ }
+ }
+
+ parser->state = prev_state;
+
+ if (buflen > 0) {
+ munmap (buf, buflen);
+ }
+
+ return res;
+}
+
+/**
+ * Handle include macro
+ * @param data include data
+ * @param len length of data
+ * @param ud user data
+ * @param err error ptr
+ * @return
+ */
+bool
+ucl_include_handler (const unsigned char *data, size_t len, void* ud)
+{
+ struct ucl_parser *parser = ud;
+
+ if (*data == '/' || *data == '.') {
+ /* Try to load a file */
+ return ucl_include_file (data, len, parser, false, true);
+ }
+
+ return ucl_include_url (data, len, parser, false, true);
+}
+
+/**
+ * Handle includes macro
+ * @param data include data
+ * @param len length of data
+ * @param ud user data
+ * @param err error ptr
+ * @return
+ */
+bool
+ucl_includes_handler (const unsigned char *data, size_t len, void* ud)
+{
+ struct ucl_parser *parser = ud;
+
+ if (*data == '/' || *data == '.') {
+ /* Try to load a file */
+ return ucl_include_file (data, len, parser, true, true);
+ }
+
+ return ucl_include_url (data, len, parser, true, true);
+}
+
+
+bool
+ucl_try_include_handler (const unsigned char *data, size_t len, void* ud)
+{
+ struct ucl_parser *parser = ud;
+
+ if (*data == '/' || *data == '.') {
+ /* Try to load a file */
+ return ucl_include_file (data, len, parser, false, false);
+ }
+
+ return ucl_include_url (data, len, parser, false, false);
+}
+
+bool
+ucl_parser_set_filevars (struct ucl_parser *parser, const char *filename, bool need_expand)
+{
+ char realbuf[PATH_MAX], *curdir;
+
+ if (filename != NULL) {
+ if (need_expand) {
+ if (realpath (filename, realbuf) == NULL) {
+ return false;
+ }
+ }
+ else {
+ ucl_strlcpy (realbuf, filename, sizeof (realbuf));
+ }
+
+ /* Define variables */
+ ucl_parser_register_variable (parser, "FILENAME", realbuf);
+ curdir = dirname (realbuf);
+ ucl_parser_register_variable (parser, "CURDIR", curdir);
+ }
+ else {
+ /* Set everything from the current dir */
+ curdir = getcwd (realbuf, sizeof (realbuf));
+ ucl_parser_register_variable (parser, "FILENAME", "undef");
+ ucl_parser_register_variable (parser, "CURDIR", curdir);
+ }
+
+ return true;
+}
+
+bool
+ucl_parser_add_file (struct ucl_parser *parser, const char *filename)
+{
+ unsigned char *buf;
+ size_t len;
+ bool ret;
+ char realbuf[PATH_MAX];
+
+ if (realpath (filename, realbuf) == NULL) {
+ ucl_create_err (&parser->err, "cannot open file %s: %s",
+ filename,
+ strerror (errno));
+ return false;
+ }
+
+ if (!ucl_fetch_file (realbuf, &buf, &len, &parser->err, true)) {
+ return false;
+ }
+
+ ucl_parser_set_filevars (parser, realbuf, false);
+ ret = ucl_parser_add_chunk (parser, buf, len);
+
+ if (len > 0) {
+ munmap (buf, len);
+ }
+
+ return ret;
+}
+
+size_t
+ucl_strlcpy (char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0') {
+ break;
+ }
+ }
+ }
+
+ if (n == 0 && siz != 0) {
+ *d = '\0';
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+
+size_t
+ucl_strlcpy_unsafe (char *dst, const char *src, size_t siz)
+{
+ memcpy (dst, src, siz - 1);
+ dst[siz - 1] = '\0';
+
+ return siz - 1;
+}
+
+size_t
+ucl_strlcpy_tolower (char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = tolower (*s++)) == '\0') {
+ break;
+ }
+ }
+ }
+
+ if (n == 0 && siz != 0) {
+ *d = '\0';
+ }
+
+ return (s - src); /* count does not include NUL */
+}
+
+ucl_object_t *
+ucl_object_fromstring_common (const char *str, size_t len, enum ucl_string_flags flags)
+{
+ ucl_object_t *obj;
+ const char *start, *end, *p, *pos;
+ char *dst, *d;
+ size_t escaped_len;
+
+ if (str == NULL) {
+ return NULL;
+ }
+
+ obj = ucl_object_new ();
+ if (obj) {
+ if (len == 0) {
+ len = strlen (str);
+ }
+ if (flags & UCL_STRING_TRIM) {
+ /* Skip leading spaces */
+ for (start = str; (size_t)(start - str) < len; start ++) {
+ if (!ucl_test_character (*start, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ break;
+ }
+ }
+ /* Skip trailing spaces */
+ for (end = str + len - 1; end > start; end --) {
+ if (!ucl_test_character (*end, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ break;
+ }
+ }
+ end ++;
+ }
+ else {
+ start = str;
+ end = str + len;
+ }
+
+ obj->type = UCL_STRING;
+ if (flags & UCL_STRING_ESCAPE) {
+ for (p = start, escaped_len = 0; p < end; p ++, escaped_len ++) {
+ if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
+ escaped_len ++;
+ }
+ }
+ dst = malloc (escaped_len + 1);
+ if (dst != NULL) {
+ for (p = start, d = dst; p < end; p ++, d ++) {
+ if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
+ switch (*p) {
+ case '\n':
+ *d++ = '\\';
+ *d = 'n';
+ break;
+ case '\r':
+ *d++ = '\\';
+ *d = 'r';
+ break;
+ case '\b':
+ *d++ = '\\';
+ *d = 'b';
+ break;
+ case '\t':
+ *d++ = '\\';
+ *d = 't';
+ break;
+ case '\f':
+ *d++ = '\\';
+ *d = 'f';
+ break;
+ case '\\':
+ *d++ = '\\';
+ *d = '\\';
+ break;
+ case '"':
+ *d++ = '\\';
+ *d = '"';
+ break;
+ }
+ }
+ else {
+ *d = *p;
+ }
+ }
+ *d = '\0';
+ obj->value.sv = dst;
+ obj->trash_stack[UCL_TRASH_VALUE] = dst;
+ obj->len = escaped_len;
+ }
+ }
+ else {
+ dst = malloc (end - start + 1);
+ if (dst != NULL) {
+ ucl_strlcpy_unsafe (dst, start, end - start + 1);
+ obj->value.sv = dst;
+ obj->trash_stack[UCL_TRASH_VALUE] = dst;
+ obj->len = end - start;
+ }
+ }
+ if ((flags & UCL_STRING_PARSE) && dst != NULL) {
+ /* Parse what we have */
+ if (flags & UCL_STRING_PARSE_BOOLEAN) {
+ if (!ucl_maybe_parse_boolean (obj, dst, obj->len) && (flags & UCL_STRING_PARSE_NUMBER)) {
+ ucl_maybe_parse_number (obj, dst, dst + obj->len, &pos,
+ flags & UCL_STRING_PARSE_DOUBLE,
+ flags & UCL_STRING_PARSE_BYTES);
+ }
+ }
+ else {
+ ucl_maybe_parse_number (obj, dst, dst + obj->len, &pos,
+ flags & UCL_STRING_PARSE_DOUBLE,
+ flags & UCL_STRING_PARSE_BYTES);
+ }
+ }
+ }
+
+ return obj;
+}
+
+static ucl_object_t *
+ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key, bool merge, bool replace)
+{
+ ucl_object_t *found, *cur;
+ ucl_object_iter_t it = NULL;
+ const char *p;
+
+ if (elt == NULL || key == NULL) {
+ return NULL;
+ }
+
+ if (top == NULL) {
+ top = ucl_object_new ();
+ top->type = UCL_OBJECT;
+ }
+
+ if (top->type != UCL_OBJECT) {
+ /* It is possible to convert NULL type to an object */
+ if (top->type == UCL_NULL) {
+ top->type = UCL_OBJECT;
+ }
+ else {
+ /* Refuse converting of other object types */
+ return top;
+ }
+ }
+
+ if (top->value.ov == NULL) {
+ top->value.ov = ucl_hash_create ();
+ }
+
+ if (keylen == 0) {
+ keylen = strlen (key);
+ }
+
+ for (p = key; p < key + keylen; p ++) {
+ if (ucl_test_character (*p, UCL_CHARACTER_UCL_UNSAFE)) {
+ elt->flags |= UCL_OBJECT_NEED_KEY_ESCAPE;
+ break;
+ }
+ }
+
+ elt->key = key;
+ elt->keylen = keylen;
+
+ if (copy_key) {
+ ucl_copy_key_trash (elt);
+ }
+
+ found = ucl_hash_search_obj (top->value.ov, elt);
+
+ if (!found) {
+ top->value.ov = ucl_hash_insert_object (top->value.ov, elt);
+ DL_APPEND (found, elt);
+ }
+ else {
+ if (replace) {
+ ucl_hash_delete (top->value.ov, found);
+ ucl_object_unref (found);
+ top->value.ov = ucl_hash_insert_object (top->value.ov, elt);
+ found = NULL;
+ DL_APPEND (found, elt);
+ }
+ else if (merge) {
+ if (found->type != UCL_OBJECT && elt->type == UCL_OBJECT) {
+ /* Insert old elt to new one */
+ elt = ucl_object_insert_key_common (elt, found, found->key, found->keylen, copy_key, false, false);
+ ucl_hash_delete (top->value.ov, found);
+ top->value.ov = ucl_hash_insert_object (top->value.ov, elt);
+ }
+ else if (found->type == UCL_OBJECT && elt->type != UCL_OBJECT) {
+ /* Insert new to old */
+ found = ucl_object_insert_key_common (found, elt, elt->key, elt->keylen, copy_key, false, false);
+ }
+ else if (found->type == UCL_OBJECT && elt->type == UCL_OBJECT) {
+ /* Mix two hashes */
+ while ((cur = ucl_iterate_object (elt, &it, true)) != NULL) {
+ ucl_object_ref (cur);
+ found = ucl_object_insert_key_common (found, cur, cur->key, cur->keylen, copy_key, false, false);
+ }
+ ucl_object_unref (elt);
+ }
+ else {
+ /* Just make a list of scalars */
+ DL_APPEND (found, elt);
+ }
+ }
+ else {
+ DL_APPEND (found, elt);
+ }
+ }
+
+ return top;
+}
+
+ucl_object_t *
+ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key)
+{
+ return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, false, false);
+}
+
+ucl_object_t *
+ucl_object_insert_key_merged (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key)
+{
+ return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, true, false);
+}
+
+ucl_object_t *
+ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt,
+ const char *key, size_t keylen, bool copy_key)
+{
+ return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, false, true);
+}
+
+ucl_object_t *
+ucl_object_find_keyl (ucl_object_t *obj, const char *key, size_t klen)
+{
+ ucl_object_t *ret, srch;
+
+ if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) {
+ return NULL;
+ }
+
+ srch.key = key;
+ srch.keylen = klen;
+ ret = ucl_hash_search_obj (obj->value.ov, &srch);
+
+ return ret;
+}
+
+ucl_object_t *
+ucl_object_find_key (ucl_object_t *obj, const char *key)
+{
+ size_t klen;
+ ucl_object_t *ret, srch;
+
+ if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) {
+ return NULL;
+ }
+
+ klen = strlen (key);
+ srch.key = key;
+ srch.keylen = klen;
+ ret = ucl_hash_search_obj (obj->value.ov, &srch);
+
+ return ret;
+}
+
+ucl_object_t*
+ucl_iterate_object (ucl_object_t *obj, ucl_object_iter_t *iter, bool expand_values)
+{
+ ucl_object_t *elt;
+
+ if (expand_values) {
+ switch (obj->type) {
+ case UCL_OBJECT:
+ return (ucl_object_t*)ucl_hash_iterate (obj->value.ov, iter);
+ break;
+ case UCL_ARRAY:
+ elt = *iter;
+ if (elt == NULL) {
+ elt = obj->value.av;
+ if (elt == NULL) {
+ return NULL;
+ }
+ }
+ else if (elt == obj->value.av) {
+ return NULL;
+ }
+ *iter = elt->next ? elt->next : obj->value.av;
+ return elt;
+ default:
+ /* Go to linear iteration */
+ break;
+ }
+ }
+ /* Treat everything as a linear list */
+ elt = *iter;
+ if (elt == NULL) {
+ elt = obj;
+ if (elt == NULL) {
+ return NULL;
+ }
+ }
+ else if (elt == obj) {
+ return NULL;
+ }
+ *iter = elt->next ? elt->next : obj;
+ return elt;
+
+ /* Not reached */
+ return NULL;
+}
diff --git a/src/xxhash.c b/src/xxhash.c
new file mode 100644
index 000000000000..fc4463395c5e
--- /dev/null
+++ b/src/xxhash.c
@@ -0,0 +1,475 @@
+/*
+xxHash - Fast Hash algorithm
+Copyright (C) 2012-2013, Yann Collet.
+BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+You can contact the author at :
+- xxHash source repository : http://code.google.com/p/xxhash/
+*/
+
+
+//**************************************
+// Tuning parameters
+//**************************************
+// Unaligned memory access is automatically enabled for "common" CPU, such as x86.
+// For others CPU, the compiler will be more cautious, and insert extra code to ensure aligned access is respected.
+// If you know your target CPU supports unaligned memory access, you want to force this option manually to improve performance.
+// You can also enable this parameter if you know your input data will always be aligned (boundaries of 4, for U32).
+#if defined(__ARM_FEATURE_UNALIGNED) || defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)
+# define XXH_USE_UNALIGNED_ACCESS 1
+#endif
+
+// XXH_ACCEPT_NULL_INPUT_POINTER :
+// If the input pointer is a null pointer, xxHash default behavior is to trigger a memory access error, since it is a bad pointer.
+// When this option is enabled, xxHash output for null input pointers will be the same as a null-length input.
+// This option has a very small performance cost (only measurable on small inputs).
+// By default, this option is disabled. To enable it, uncomment below define :
+//#define XXH_ACCEPT_NULL_INPUT_POINTER 1
+
+// XXH_FORCE_NATIVE_FORMAT :
+// By default, xxHash library provides endian-independant Hash values, based on little-endian convention.
+// Results are therefore identical for little-endian and big-endian CPU.
+// This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format.
+// Should endian-independance be of no importance for your application, you may set the #define below to 1.
+// It will improve speed for Big-endian CPU.
+// This option has no impact on Little_Endian CPU.
+#define XXH_FORCE_NATIVE_FORMAT 0
+
+
+//**************************************
+// Compiler Specific Options
+//**************************************
+// Disable some Visual warning messages
+#ifdef _MSC_VER // Visual Studio
+# pragma warning(disable : 4127) // disable: C4127: conditional expression is constant
+#endif
+
+#ifdef _MSC_VER // Visual Studio
+# define forceinline static __forceinline
+#else
+# ifdef __GNUC__
+# define forceinline static inline __attribute__((always_inline))
+# else
+# define forceinline static inline
+# endif
+#endif
+
+
+//**************************************
+// Includes & Memory related functions
+//**************************************
+#include "xxhash.h"
+// Modify the local functions below should you wish to use some other memory related routines
+// for malloc(), free()
+#include <stdlib.h>
+forceinline void* XXH_malloc(size_t s) { return malloc(s); }
+forceinline void XXH_free (void* p) { free(p); }
+// for memcpy()
+#include <string.h>
+forceinline void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); }
+
+
+//**************************************
+// Basic Types
+//**************************************
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L // C99
+# include <stdint.h>
+ typedef uint8_t BYTE;
+ typedef uint16_t U16;
+ typedef uint32_t U32;
+ typedef int32_t S32;
+ typedef uint64_t U64;
+#else
+ typedef unsigned char BYTE;
+ typedef unsigned short U16;
+ typedef unsigned int U32;
+ typedef signed int S32;
+ typedef unsigned long long U64;
+#endif
+
+#if defined(__GNUC__) && !defined(XXH_USE_UNALIGNED_ACCESS)
+# define _PACKED __attribute__ ((packed))
+#else
+# define _PACKED
+#endif
+
+#if !defined(XXH_USE_UNALIGNED_ACCESS) && !defined(__GNUC__)
+# ifdef __IBMC__
+# pragma pack(1)
+# else
+# pragma pack(push, 1)
+# endif
+#endif
+
+typedef struct _U32_S { U32 v; } _PACKED U32_S;
+
+#if !defined(XXH_USE_UNALIGNED_ACCESS) && !defined(__GNUC__)
+# pragma pack(pop)
+#endif
+
+#define A32(x) (((U32_S *)(x))->v)
+
+
+//***************************************
+// Compiler-specific Functions and Macros
+//***************************************
+#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+
+// Note : although _rotl exists for minGW (GCC under windows), performance seems poor
+#if defined(_MSC_VER)
+# define XXH_rotl32(x,r) _rotl(x,r)
+#else
+# define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r)))
+#endif
+
+#if defined(_MSC_VER) // Visual Studio
+# define XXH_swap32 _byteswap_ulong
+#elif GCC_VERSION >= 403
+# define XXH_swap32 __builtin_bswap32
+#else
+static inline U32 XXH_swap32 (U32 x) {
+ return ((x << 24) & 0xff000000 ) |
+ ((x << 8) & 0x00ff0000 ) |
+ ((x >> 8) & 0x0000ff00 ) |
+ ((x >> 24) & 0x000000ff );}
+#endif
+
+
+//**************************************
+// Constants
+//**************************************
+#define PRIME32_1 2654435761U
+#define PRIME32_2 2246822519U
+#define PRIME32_3 3266489917U
+#define PRIME32_4 668265263U
+#define PRIME32_5 374761393U
+
+
+//**************************************
+// Architecture Macros
+//**************************************
+typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess;
+#ifndef XXH_CPU_LITTLE_ENDIAN // It is possible to define XXH_CPU_LITTLE_ENDIAN externally, for example using a compiler switch
+ static const int one = 1;
+# define XXH_CPU_LITTLE_ENDIAN (*(char*)(&one))
+#endif
+
+
+//**************************************
+// Macros
+//**************************************
+#define XXH_STATIC_ASSERT(c) { enum { XXH_static_assert = 1/(!!(c)) }; } // use only *after* variable declarations
+
+
+//****************************
+// Memory reads
+//****************************
+typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment;
+
+forceinline U32 XXH_readLE32_align(const U32* ptr, XXH_endianess endian, XXH_alignment align)
+{
+ if (align==XXH_unaligned)
+ return endian==XXH_littleEndian ? A32(ptr) : XXH_swap32(A32(ptr));
+ else
+ return endian==XXH_littleEndian ? *ptr : XXH_swap32(*ptr);
+}
+
+forceinline U32 XXH_readLE32(const U32* ptr, XXH_endianess endian) { return XXH_readLE32_align(ptr, endian, XXH_unaligned); }
+
+
+//****************************
+// Simple Hash Functions
+//****************************
+forceinline U32 XXH32_endian_align(const void* input, int len, U32 seed, XXH_endianess endian, XXH_alignment align)
+{
+ const BYTE* p = (const BYTE*)input;
+ const BYTE* const bEnd = p + len;
+ U32 h32;
+
+#ifdef XXH_ACCEPT_NULL_INPUT_POINTER
+ if (p==NULL) { len=0; p=(const BYTE*)(size_t)16; }
+#endif
+
+ if (len>=16)
+ {
+ const BYTE* const limit = bEnd - 16;
+ U32 v1 = seed + PRIME32_1 + PRIME32_2;
+ U32 v2 = seed + PRIME32_2;
+ U32 v3 = seed + 0;
+ U32 v4 = seed - PRIME32_1;
+
+ do
+ {
+ v1 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_2; v1 = XXH_rotl32(v1, 13); v1 *= PRIME32_1; p+=4;
+ v2 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_2; v2 = XXH_rotl32(v2, 13); v2 *= PRIME32_1; p+=4;
+ v3 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_2; v3 = XXH_rotl32(v3, 13); v3 *= PRIME32_1; p+=4;
+ v4 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_2; v4 = XXH_rotl32(v4, 13); v4 *= PRIME32_1; p+=4;
+ } while (p<=limit);
+
+ h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18);
+ }
+ else
+ {
+ h32 = seed + PRIME32_5;
+ }
+
+ h32 += (U32) len;
+
+ while (p<=bEnd-4)
+ {
+ h32 += XXH_readLE32_align((const U32*)p, endian, align) * PRIME32_3;
+ h32 = XXH_rotl32(h32, 17) * PRIME32_4 ;
+ p+=4;
+ }
+
+ while (p<bEnd)
+ {
+ h32 += (*p) * PRIME32_5;
+ h32 = XXH_rotl32(h32, 11) * PRIME32_1 ;
+ p++;
+ }
+
+ h32 ^= h32 >> 15;
+ h32 *= PRIME32_2;
+ h32 ^= h32 >> 13;
+ h32 *= PRIME32_3;
+ h32 ^= h32 >> 16;
+
+ return h32;
+}
+
+
+U32 XXH32(const void* input, int len, U32 seed)
+{
+#if 0
+ // Simple version, good for code maintenance, but unfortunately slow for small inputs
+ void* state = XXH32_init(seed);
+ XXH32_update(state, input, len);
+ return XXH32_digest(state);
+#else
+ XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+# if !defined(XXH_USE_UNALIGNED_ACCESS)
+ if (!(((size_t)input) & 3)) // Input is aligned, let's leverage the speed advantage
+ {
+ if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+ return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned);
+ else
+ return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned);
+ }
+# endif
+
+ if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+ return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned);
+ else
+ return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned);
+#endif
+}
+
+
+//****************************
+// Advanced Hash Functions
+//****************************
+
+struct XXH_state32_t
+{
+ U64 total_len;
+ U32 seed;
+ U32 v1;
+ U32 v2;
+ U32 v3;
+ U32 v4;
+ int memsize;
+ char memory[16];
+};
+
+
+int XXH32_sizeofState(void)
+{
+ XXH_STATIC_ASSERT(XXH32_SIZEOFSTATE >= sizeof(struct XXH_state32_t)); // A compilation error here means XXH32_SIZEOFSTATE is not large enough
+ return sizeof(struct XXH_state32_t);
+}
+
+
+XXH_errorcode XXH32_resetState(void* state_in, U32 seed)
+{
+ struct XXH_state32_t * state = (struct XXH_state32_t *) state_in;
+ state->seed = seed;
+ state->v1 = seed + PRIME32_1 + PRIME32_2;
+ state->v2 = seed + PRIME32_2;
+ state->v3 = seed + 0;
+ state->v4 = seed - PRIME32_1;
+ state->total_len = 0;
+ state->memsize = 0;
+ return XXH_OK;
+}
+
+
+void* XXH32_init (U32 seed)
+{
+ void* state = XXH_malloc (sizeof(struct XXH_state32_t));
+ XXH32_resetState(state, seed);
+ return state;
+}
+
+
+forceinline XXH_errorcode XXH32_update_endian (void* state_in, const void* input, int len, XXH_endianess endian)
+{
+ struct XXH_state32_t * state = (struct XXH_state32_t *) state_in;
+ const BYTE* p = (const BYTE*)input;
+ const BYTE* const bEnd = p + len;
+
+#ifdef XXH_ACCEPT_NULL_INPUT_POINTER
+ if (input==NULL) return XXH_ERROR;
+#endif
+
+ state->total_len += len;
+
+ if (state->memsize + len < 16) // fill in tmp buffer
+ {
+ XXH_memcpy(state->memory + state->memsize, input, len);
+ state->memsize += len;
+ return XXH_OK;
+ }
+
+ if (state->memsize) // some data left from previous update
+ {
+ XXH_memcpy(state->memory + state->memsize, input, 16-state->memsize);
+ {
+ const U32* p32 = (const U32*)state->memory;
+ state->v1 += XXH_readLE32(p32, endian) * PRIME32_2; state->v1 = XXH_rotl32(state->v1, 13); state->v1 *= PRIME32_1; p32++;
+ state->v2 += XXH_readLE32(p32, endian) * PRIME32_2; state->v2 = XXH_rotl32(state->v2, 13); state->v2 *= PRIME32_1; p32++;
+ state->v3 += XXH_readLE32(p32, endian) * PRIME32_2; state->v3 = XXH_rotl32(state->v3, 13); state->v3 *= PRIME32_1; p32++;
+ state->v4 += XXH_readLE32(p32, endian) * PRIME32_2; state->v4 = XXH_rotl32(state->v4, 13); state->v4 *= PRIME32_1; p32++;
+ }
+ p += 16-state->memsize;
+ state->memsize = 0;
+ }
+
+ if (p <= bEnd-16)
+ {
+ const BYTE* const limit = bEnd - 16;
+ U32 v1 = state->v1;
+ U32 v2 = state->v2;
+ U32 v3 = state->v3;
+ U32 v4 = state->v4;
+
+ do
+ {
+ v1 += XXH_readLE32((const U32*)p, endian) * PRIME32_2; v1 = XXH_rotl32(v1, 13); v1 *= PRIME32_1; p+=4;
+ v2 += XXH_readLE32((const U32*)p, endian) * PRIME32_2; v2 = XXH_rotl32(v2, 13); v2 *= PRIME32_1; p+=4;
+ v3 += XXH_readLE32((const U32*)p, endian) * PRIME32_2; v3 = XXH_rotl32(v3, 13); v3 *= PRIME32_1; p+=4;
+ v4 += XXH_readLE32((const U32*)p, endian) * PRIME32_2; v4 = XXH_rotl32(v4, 13); v4 *= PRIME32_1; p+=4;
+ } while (p<=limit);
+
+ state->v1 = v1;
+ state->v2 = v2;
+ state->v3 = v3;
+ state->v4 = v4;
+ }
+
+ if (p < bEnd)
+ {
+ XXH_memcpy(state->memory, p, bEnd-p);
+ state->memsize = (int)(bEnd-p);
+ }
+
+ return XXH_OK;
+}
+
+XXH_errorcode XXH32_update (void* state_in, const void* input, int len)
+{
+ XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+ if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+ return XXH32_update_endian(state_in, input, len, XXH_littleEndian);
+ else
+ return XXH32_update_endian(state_in, input, len, XXH_bigEndian);
+}
+
+
+
+forceinline U32 XXH32_intermediateDigest_endian (void* state_in, XXH_endianess endian)
+{
+ struct XXH_state32_t * state = (struct XXH_state32_t *) state_in;
+ const BYTE * p = (const BYTE*)state->memory;
+ BYTE* bEnd = (BYTE*)state->memory + state->memsize;
+ U32 h32;
+
+ if (state->total_len >= 16)
+ {
+ h32 = XXH_rotl32(state->v1, 1) + XXH_rotl32(state->v2, 7) + XXH_rotl32(state->v3, 12) + XXH_rotl32(state->v4, 18);
+ }
+ else
+ {
+ h32 = state->seed + PRIME32_5;
+ }
+
+ h32 += (U32) state->total_len;
+
+ while (p<=bEnd-4)
+ {
+ h32 += XXH_readLE32((const U32*)p, endian) * PRIME32_3;
+ h32 = XXH_rotl32(h32, 17) * PRIME32_4;
+ p+=4;
+ }
+
+ while (p<bEnd)
+ {
+ h32 += (*p) * PRIME32_5;
+ h32 = XXH_rotl32(h32, 11) * PRIME32_1;
+ p++;
+ }
+
+ h32 ^= h32 >> 15;
+ h32 *= PRIME32_2;
+ h32 ^= h32 >> 13;
+ h32 *= PRIME32_3;
+ h32 ^= h32 >> 16;
+
+ return h32;
+}
+
+
+U32 XXH32_intermediateDigest (void* state_in)
+{
+ XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+ if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+ return XXH32_intermediateDigest_endian(state_in, XXH_littleEndian);
+ else
+ return XXH32_intermediateDigest_endian(state_in, XXH_bigEndian);
+}
+
+
+U32 XXH32_digest (void* state_in)
+{
+ U32 h32 = XXH32_intermediateDigest(state_in);
+
+ XXH_free(state_in);
+
+ return h32;
+}
diff --git a/src/xxhash.h b/src/xxhash.h
new file mode 100644
index 000000000000..b89238883e23
--- /dev/null
+++ b/src/xxhash.h
@@ -0,0 +1,164 @@
+/*
+ xxHash - Fast Hash algorithm
+ Header File
+ Copyright (C) 2012-2013, Yann Collet.
+ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ You can contact the author at :
+ - xxHash source repository : http://code.google.com/p/xxhash/
+*/
+
+/* Notice extracted from xxHash homepage :
+
+xxHash is an extremely fast Hash algorithm, running at RAM speed limits.
+It also successfully passes all tests from the SMHasher suite.
+
+Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz)
+
+Name Speed Q.Score Author
+xxHash 5.4 GB/s 10
+CrapWow 3.2 GB/s 2 Andrew
+MumurHash 3a 2.7 GB/s 10 Austin Appleby
+SpookyHash 2.0 GB/s 10 Bob Jenkins
+SBox 1.4 GB/s 9 Bret Mulvey
+Lookup3 1.2 GB/s 9 Bob Jenkins
+SuperFastHash 1.2 GB/s 1 Paul Hsieh
+CityHash64 1.05 GB/s 10 Pike & Alakuijala
+FNV 0.55 GB/s 5 Fowler, Noll, Vo
+CRC32 0.43 GB/s 9
+MD5-32 0.33 GB/s 10 Ronald L. Rivest
+SHA1-32 0.28 GB/s 10
+
+Q.Score is a measure of quality of the hash function.
+It depends on successfully passing SMHasher test set.
+10 is a perfect score.
+*/
+
+#pragma once
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+
+//****************************
+// Type
+//****************************
+typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode;
+
+
+
+//****************************
+// Simple Hash Functions
+//****************************
+
+unsigned int XXH32 (const void* input, int len, unsigned int seed);
+
+/*
+XXH32() :
+ Calculate the 32-bits hash of sequence of length "len" stored at memory address "input".
+ The memory between input & input+len must be valid (allocated and read-accessible).
+ "seed" can be used to alter the result predictably.
+ This function successfully passes all SMHasher tests.
+ Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s
+ Note that "len" is type "int", which means it is limited to 2^31-1.
+ If your data is larger, use the advanced functions below.
+*/
+
+
+
+//****************************
+// Advanced Hash Functions
+//****************************
+
+void* XXH32_init (unsigned int seed);
+XXH_errorcode XXH32_update (void* state, const void* input, int len);
+unsigned int XXH32_digest (void* state);
+
+/*
+These functions calculate the xxhash of an input provided in several small packets,
+as opposed to an input provided as a single block.
+
+It must be started with :
+void* XXH32_init()
+The function returns a pointer which holds the state of calculation.
+
+This pointer must be provided as "void* state" parameter for XXH32_update().
+XXH32_update() can be called as many times as necessary.
+The user must provide a valid (allocated) input.
+The function returns an error code, with 0 meaning OK, and any other value meaning there is an error.
+Note that "len" is type "int", which means it is limited to 2^31-1.
+If your data is larger, it is recommended to chunk your data into blocks
+of size for example 2^30 (1GB) to avoid any "int" overflow issue.
+
+Finally, you can end the calculation anytime, by using XXH32_digest().
+This function returns the final 32-bits hash.
+You must provide the same "void* state" parameter created by XXH32_init().
+Memory will be freed by XXH32_digest().
+*/
+
+
+int XXH32_sizeofState(void);
+XXH_errorcode XXH32_resetState(void* state, unsigned int seed);
+
+#define XXH32_SIZEOFSTATE 48
+typedef struct { long long ll[(XXH32_SIZEOFSTATE+(sizeof(long long)-1))/sizeof(long long)]; } XXH32_stateSpace_t;
+/*
+These functions allow user application to make its own allocation for state.
+
+XXH32_sizeofState() is used to know how much space must be allocated for the xxHash 32-bits state.
+Note that the state must be aligned to access 'long long' fields. Memory must be allocated and referenced by a pointer.
+This pointer must then be provided as 'state' into XXH32_resetState(), which initializes the state.
+
+For static allocation purposes (such as allocation on stack, or freestanding systems without malloc()),
+use the structure XXH32_stateSpace_t, which will ensure that memory space is large enough and correctly aligned to access 'long long' fields.
+*/
+
+
+unsigned int XXH32_intermediateDigest (void* state);
+/*
+This function does the same as XXH32_digest(), generating a 32-bit hash,
+but preserve memory context.
+This way, it becomes possible to generate intermediate hashes, and then continue feeding data with XXH32_update().
+To free memory context, use XXH32_digest(), or free().
+*/
+
+
+
+//****************************
+// Deprecated function names
+//****************************
+// The following translations are provided to ease code transition
+// You are encouraged to no longer this function names
+#define XXH32_feed XXH32_update
+#define XXH32_result XXH32_digest
+#define XXH32_getIntermediateResult XXH32_intermediateDigest
+
+
+
+#if defined (__cplusplus)
+}
+#endif
diff --git a/tests/1.in b/tests/1.in
new file mode 100644
index 000000000000..9c14df6c547c
--- /dev/null
+++ b/tests/1.in
@@ -0,0 +1,14 @@
+{
+"key1": value;
+"key1": value2;
+"key1": "value;"
+"key1": 1.0,
+"key1": -0xdeadbeef
+"key1": 0xdeadbeef.1
+"key1": 0xreadbeef
+"key1": -1e-10,
+"key1": 1
+"key1": true
+"key1": no
+"key1": yes
+}
diff --git a/tests/1.res b/tests/1.res
new file mode 100644
index 000000000000..660af9fb1988
--- /dev/null
+++ b/tests/1.res
@@ -0,0 +1,13 @@
+key1 = "value";
+key1 = "value2";
+key1 = "value;";
+key1 = 1.0;
+key1 = -3735928559;
+key1 = "0xdeadbeef.1";
+key1 = "0xreadbeef";
+key1 = -1e-10;
+key1 = 1;
+key1 = true;
+key1 = false;
+key1 = true;
+
diff --git a/tests/2.in b/tests/2.in
new file mode 100644
index 000000000000..a4419a4f5e07
--- /dev/null
+++ b/tests/2.in
@@ -0,0 +1,24 @@
+section1 { param1 = value; param2 = value,
+section3 {param = value; param2 = value, param3 = ["value1", value2, 100500]}}
+section2 { param1 = {key = value}, param1 = ["key"]}
+
+# Numbers
+key1 = 1s
+key2 = 1min
+key3 = 1kb
+key4 = 5M
+key5 = 10mS
+key6 = 10y
+
+# Strings
+key1 = "some string";
+key2 = /some/path;
+key3 = 111some,
+key4: s1,
+"key5": "\n\r123"
+
+# Variables
+keyvar = "$ABItest";
+keyvar = "${ABI}$ABI${ABI}${$ABI}";
+keyvar = "${some}$no${}$$test$$$$$$$";
+keyvar = "$ABI$$ABI$$$ABI$$$$";
diff --git a/tests/2.res b/tests/2.res
new file mode 100644
index 000000000000..72e95705ec46
--- /dev/null
+++ b/tests/2.res
@@ -0,0 +1,37 @@
+section1 {
+ param1 = "value";
+ param2 = "value";
+ section3 {
+ param = "value";
+ param2 = "value";
+ param3 [
+ "value1",
+ "value2",
+ 100500,
+ ]
+ }
+}
+section2 {
+ param1 {
+ key = "value";
+ }
+ param1 [
+ "key",
+ ]
+}
+key1 = 1.0;
+key1 = "some string";
+key2 = 60.0;
+key2 = "/some/path";
+key3 = 1024;
+key3 = "111some";
+key4 = 5000000;
+key4 = "s1";
+key5 = 0.010000;
+key5 = "\n\r123";
+key6 = 2207520000.000000;
+keyvar = "unknowntest";
+keyvar = "unknownunknownunknown${unknown}";
+keyvar = "${some}$no${}$$test$$$$$$$";
+keyvar = "unknown$ABI$unknown$$";
+
diff --git a/tests/3.in b/tests/3.in
new file mode 100644
index 000000000000..b3e369670140
--- /dev/null
+++ b/tests/3.in
@@ -0,0 +1,31 @@
+/*
+ * Pkg conf
+ */
+
+#packagesite http//pkg.freebsd.org/freebsd-9-amd64/latest
+#packagesite http//pkg.freebsd.org/freebsd-9-amd64/latest
+packagesite: http://pkg-test.freebsd.org/pkg-test/${ABI}/latest
+squaretest: some[]value
+ALIAS : {
+ all-depends: query %dn-%dv,
+ annotations: info -A,
+ build-depends: info -qd,
+ download: fetch,
+ iinfo: info -i -g -x,
+ isearch: search -i -g -x,
+ leaf: query -e '%a == 0' '%n-%v',
+ leaf: query -e '%a == 0' '%n-%v',
+ list: info -ql,
+ origin: info -qo,
+ provided-depends: info -qb,
+ raw: info -R,
+ required-depends: info -qr,
+ shared-depends: info -qB,
+ show: info -f -k,
+ size: info -sq,
+ }
+
+repo_dirs : [
+ /home/bapt,
+ /usr/local/etc
+]
diff --git a/tests/3.res b/tests/3.res
new file mode 100644
index 000000000000..bb1845768754
--- /dev/null
+++ b/tests/3.res
@@ -0,0 +1,25 @@
+packagesite = "http://pkg-test.freebsd.org/pkg-test/unknown/latest";
+squaretest = "some[]value";
+alias {
+ all-depends = "query %dn-%dv";
+ annotations = "info -A";
+ build-depends = "info -qd";
+ download = "fetch";
+ iinfo = "info -i -g -x";
+ isearch = "search -i -g -x";
+ leaf = "query -e '%a == 0' '%n-%v'";
+ leaf = "query -e '%a == 0' '%n-%v'";
+ list = "info -ql";
+ origin = "info -qo";
+ provided-depends = "info -qb";
+ raw = "info -R";
+ required-depends = "info -qr";
+ shared-depends = "info -qB";
+ show = "info -f -k";
+ size = "info -sq";
+}
+repo_dirs [
+ "/home/bapt",
+ "/usr/local/etc",
+]
+
diff --git a/tests/4.in b/tests/4.in
new file mode 100644
index 000000000000..2b296efffa83
--- /dev/null
+++ b/tests/4.in
@@ -0,0 +1,47 @@
+name : "pkgconf"
+version : "0.9.3"
+origin : "devel/pkgconf"
+comment : "Utility to help to configure compiler and linker flags"
+arch : "freebsd:9:x86:64"
+maintainer : "bapt@FreeBSD.org"
+prefix : "/usr/local"
+licenselogic : "single"
+licenses : [
+ "BSD",
+]
+flatsize : 60523
+desc : "pkgconf is a program which helps to configure compiler and linker flags for\ndevelopment frameworks. It is similar to pkg-config, but was written from\nscratch in Summer of 2011 to replace pkg-config, which now needs itself to build\nitself.\n\nWWW: https://github.com/pkgconf/pkgconf"
+categories : [
+ "devel",
+]
+files : {
+ /usr/local/bin/pkg-config : "-",
+ /usr/local/bin/pkgconf : "4a0fc53e5ad64e8085da2e61652d61c50b192a086421d865703f1de9f724da38",
+ /usr/local/share/aclocal/pkg.m4 : "cffab33d659adfe36497ec57665eec36fa6fb7b007e578e6ac2434cc28be8820",
+ /usr/local/share/licenses/pkgconf-0.9.3/BSD : "85e7a53b5e2d3e350e2d084fed2f94b7f63005f8e1168740e1e84aa9fa5d48ce",
+ /usr/local/share/licenses/pkgconf-0.9.3/LICENSE : "d9cce0db43502eb1bd8fbef7e960cfaa43b5647186f7f7379923b336209fd77b",
+ /usr/local/share/licenses/pkgconf-0.9.3/catalog.mk : "e7b131acce7c3d3c61f2214607b11b34526e03b05afe89a608f50586a898e2ef",
+}
+directories : {
+ /usr/local/share/licenses/pkgconf-0.9.3/ : false,
+ /usr/local/share/licenses/ : true,
+}
+scripts : {
+ post-install : "cd /usr/local\nn",
+ pre-deinstall : "cd /usr/local\nn",
+ post-deinstall : "cd /usr/local\nn",
+}
+multiline-key : <<EOD
+test
+test
+test\n
+/* comment like */
+# Some invalid endings
+ EOD
+EOD
+EOF
+# Valid ending + empty string
+
+EOD
+
+normal-key : <<EODnot
diff --git a/tests/4.res b/tests/4.res
new file mode 100644
index 000000000000..58c3599432b8
--- /dev/null
+++ b/tests/4.res
@@ -0,0 +1,36 @@
+name = "pkgconf";
+version = "0.9.3";
+origin = "devel/pkgconf";
+comment = "Utility to help to configure compiler and linker flags";
+arch = "freebsd:9:x86:64";
+maintainer = "bapt@FreeBSD.org";
+prefix = "/usr/local";
+licenselogic = "single";
+licenses [
+ "BSD",
+]
+flatsize = 60523;
+desc = "pkgconf is a program which helps to configure compiler and linker flags for\ndevelopment frameworks. It is similar to pkg-config, but was written from\nscratch in Summer of 2011 to replace pkg-config, which now needs itself to build\nitself.\n\nWWW: https://github.com/pkgconf/pkgconf";
+categories [
+ "devel",
+]
+files {
+ /usr/local/bin/pkg-config = "-";
+ /usr/local/bin/pkgconf = "4a0fc53e5ad64e8085da2e61652d61c50b192a086421d865703f1de9f724da38";
+ /usr/local/share/aclocal/pkg.m4 = "cffab33d659adfe36497ec57665eec36fa6fb7b007e578e6ac2434cc28be8820";
+ /usr/local/share/licenses/pkgconf-0.9.3/bsd = "85e7a53b5e2d3e350e2d084fed2f94b7f63005f8e1168740e1e84aa9fa5d48ce";
+ /usr/local/share/licenses/pkgconf-0.9.3/license = "d9cce0db43502eb1bd8fbef7e960cfaa43b5647186f7f7379923b336209fd77b";
+ /usr/local/share/licenses/pkgconf-0.9.3/catalog.mk = "e7b131acce7c3d3c61f2214607b11b34526e03b05afe89a608f50586a898e2ef";
+}
+directories {
+ /usr/local/share/licenses/pkgconf-0.9.3/ = false;
+ /usr/local/share/licenses/ = true;
+}
+scripts {
+ post-install = "cd /usr/local\nn";
+ pre-deinstall = "cd /usr/local\nn";
+ post-deinstall = "cd /usr/local\nn";
+}
+multiline-key = "test\ntest\ntest\\n\n/* comment like */\n# Some invalid endings\n EOD\nEOD \nEOF\n# Valid ending + empty string\n";
+normal-key = "<<EODnot";
+
diff --git a/tests/5.in b/tests/5.in
new file mode 100644
index 000000000000..83c831f0b085
--- /dev/null
+++ b/tests/5.in
@@ -0,0 +1 @@
+# test
diff --git a/tests/5.res b/tests/5.res
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/tests/5.res
@@ -0,0 +1 @@
+
diff --git a/tests/6.in b/tests/6.in
new file mode 100644
index 000000000000..5b460888ad26
--- /dev/null
+++ b/tests/6.in
@@ -0,0 +1,4 @@
+
+# test
+#
+key = value
diff --git a/tests/6.res b/tests/6.res
new file mode 100644
index 000000000000..4b17c4b4eb1b
--- /dev/null
+++ b/tests/6.res
@@ -0,0 +1,2 @@
+key = "value";
+
diff --git a/tests/7.in b/tests/7.in
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/tests/7.in
diff --git a/tests/7.res b/tests/7.res
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/tests/7.res
@@ -0,0 +1 @@
+
diff --git a/tests/8.in b/tests/8.in
new file mode 100644
index 000000000000..5559b3c9ef7a
--- /dev/null
+++ b/tests/8.in
@@ -0,0 +1,21 @@
+section blah { # test
+ param = "value"
+}
+section test {
+ key = test;
+ subsection testsub {
+ flag on;
+ subsubsection testsubsub1 testsubsub2 {
+ key = [1, 2, 3];
+ }
+ }
+}
+
+section test {
+ /* Empty */
+}
+
+
+section foo { # test
+ param = 123.2;
+}
diff --git a/tests/8.res b/tests/8.res
new file mode 100644
index 000000000000..36eb39a44e70
--- /dev/null
+++ b/tests/8.res
@@ -0,0 +1,36 @@
+section {
+ blah {
+ param = "value";
+ }
+}
+section {
+ test {
+ key = "test";
+ subsection {
+ testsub {
+ flag = true;
+ subsubsection {
+ testsubsub1 {
+ testsubsub2 {
+ key [
+ 1,
+ 2,
+ 3,
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+}
+section {
+ test {
+ }
+}
+section {
+ foo {
+ param = 123.200000;
+ }
+}
+
diff --git a/tests/9-comment.inc b/tests/9-comment.inc
new file mode 100644
index 000000000000..8939db18b91c
--- /dev/null
+++ b/tests/9-comment.inc
@@ -0,0 +1 @@
+#key = value
diff --git a/tests/9-empty.inc b/tests/9-empty.inc
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/tests/9-empty.inc
diff --git a/tests/9.in b/tests/9.in
new file mode 100644
index 000000000000..da04e40408a9
--- /dev/null
+++ b/tests/9.in
@@ -0,0 +1,13 @@
+.include "$CURDIR/9.inc"
+.include "$CURDIR/9-empty.inc"
+.include "$CURDIR/9-comment.inc"
+#.include "$CURDIR/9.inc"
+.include "$CURDIR/9.inc"
+
+key = value;
+.include "$CURDIR/9.inc"
+
+.try_include "/non/existent"
+#.try_include "$CURDIR/9.incorrect.inc"
+# 9.incorrect.inc contains '{}}'
+#key = value;
diff --git a/tests/9.inc b/tests/9.inc
new file mode 100644
index 000000000000..c5592b872310
--- /dev/null
+++ b/tests/9.inc
@@ -0,0 +1 @@
+key1 = value
diff --git a/tests/9.res b/tests/9.res
new file mode 100644
index 000000000000..ec3f014e60d4
--- /dev/null
+++ b/tests/9.res
@@ -0,0 +1,5 @@
+key1 = "value";
+key1 = "value";
+key1 = "value";
+key = "value";
+
diff --git a/tests/generate.res b/tests/generate.res
new file mode 100644
index 000000000000..1d39478e0239
--- /dev/null
+++ b/tests/generate.res
@@ -0,0 +1,20 @@
+key1 = "test string";
+key2 = "test \\nstring";
+key3 = " test string \n";
+key4 [
+ 9.999000,
+ 10,
+ 10.100000,
+]
+key4 = true;
+key5 = "";
+key6 = "";
+key7 = " \\n";
+key8 = 1048576;
+key9 = 3.140000;
+key10 = true;
+key11 = false;
+key12 = "gslin@gslin.org";
+key13 = "#test";
+"k=3" = true;
+
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
new file mode 100755
index 000000000000..6c7751ed13a9
--- /dev/null
+++ b/tests/run_tests.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+if [ $# -lt 1 ] ; then
+ echo 'Specify binary to run as the first argument'
+ exit 1
+fi
+
+
+for _tin in ${TEST_DIR}/*.in ; do
+ _t=`echo $_tin | sed -e 's/.in$//'`
+ $1 $_t.in $_t.out
+ if [ $? -ne 0 ] ; then
+ echo "Test: $_t failed, output:"
+ cat $_t.out
+ rm $_t.out
+ exit 1
+ fi
+ if [ -f $_t.res ] ; then
+ diff -s $_t.out $_t.res -u 2>/dev/null
+ if [ $? -ne 0 ] ; then
+ rm $_t.out
+ echo "Test: $_t output missmatch"
+ exit 1
+ fi
+ fi
+ rm $_t.out
+done
+
+if [ $# -gt 2 ] ; then
+ $3 ${TEST_DIR}/generate.out
+ diff -s ${TEST_DIR}/generate.out ${TEST_DIR}/generate.res -u 2>/dev/null
+ if [ $? -ne 0 ] ; then
+ rm ${TEST_DIR}/generate.out
+ echo "Test: generate.res output missmatch"
+ exit 1
+ fi
+ rm ${TEST_DIR}/generate.out
+fi
+
+sh -c "xz -c < /dev/null > /dev/null"
+if [ $? -eq 0 -a $# -gt 1 ] ; then
+ echo 'Running speed tests'
+ for _tin in ${TEST_DIR}/*.xz ; do
+ echo "Unpacking $_tin..."
+ xz -cd < $_tin > ${TEST_DIR}/test_file
+ # Preread file to cheat benchmark!
+ cat ${TEST_DIR}/test_file > /dev/null
+ echo "Starting benchmarking for $_tin..."
+ $2 ${TEST_DIR}/test_file
+ if [ $? -ne 0 ] ; then
+ echo "Test: $_tin failed"
+ rm ${TEST_DIR}/test_file
+ exit 1
+ fi
+ rm ${TEST_DIR}/test_file
+ done
+fi
+
diff --git a/tests/test_basic.c b/tests/test_basic.c
new file mode 100644
index 000000000000..641679fd2393
--- /dev/null
+++ b/tests/test_basic.c
@@ -0,0 +1,151 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ucl.h"
+
+int
+main (int argc, char **argv)
+{
+ char inbuf[8192], *test_in = NULL;
+ struct ucl_parser *parser = NULL, *parser2 = NULL;
+ ucl_object_t *obj;
+ FILE *in, *out;
+ unsigned char *emitted = NULL;
+ const char *fname_in = NULL, *fname_out = NULL;
+ int ret = 0, inlen, opt, json = 0;
+
+ while ((opt = getopt(argc, argv, "j")) != -1) {
+ switch (opt) {
+ case 'j':
+ json = 1;
+ break;
+ default: /* '?' */
+ fprintf (stderr, "Usage: %s [-j] [in] [out]\n",
+ argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ switch (argc) {
+ case 1:
+ fname_in = argv[0];
+ break;
+ case 2:
+ fname_in = argv[0];
+ fname_out = argv[1];
+ break;
+ }
+
+ if (fname_in != NULL) {
+ in = fopen (fname_in, "r");
+ if (in == NULL) {
+ exit (-errno);
+ }
+ }
+ else {
+ in = stdin;
+ }
+ parser = ucl_parser_new (UCL_PARSER_KEY_LOWERCASE);
+ ucl_parser_register_variable (parser, "ABI", "unknown");
+
+ if (fname_in != NULL) {
+ ucl_parser_set_filevars (parser, fname_in, true);
+ }
+
+ while (!feof (in)) {
+ memset (inbuf, 0, sizeof (inbuf));
+ (void)fread (inbuf, sizeof (inbuf) - 1, 1, in);
+ inlen = strlen (inbuf);
+ test_in = malloc (inlen);
+ memcpy (test_in, inbuf, inlen);
+ ucl_parser_add_chunk (parser, test_in, inlen);
+ }
+ fclose (in);
+
+ if (fname_out != NULL) {
+ out = fopen (fname_out, "w");
+ if (out == NULL) {
+ exit (-errno);
+ }
+ }
+ else {
+ out = stdout;
+ }
+ if (ucl_parser_get_error(parser) != NULL) {
+ fprintf (out, "Error occurred: %s\n", ucl_parser_get_error(parser));
+ ret = 1;
+ goto end;
+ }
+ obj = ucl_parser_get_object (parser);
+ if (json) {
+ emitted = ucl_object_emit (obj, UCL_EMIT_JSON);
+ }
+ else {
+ emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG);
+ }
+ ucl_parser_free (parser);
+ ucl_object_unref (obj);
+ parser2 = ucl_parser_new (UCL_PARSER_KEY_LOWERCASE);
+ ucl_parser_add_chunk (parser2, emitted, strlen (emitted));
+
+ if (ucl_parser_get_error(parser2) != NULL) {
+ fprintf (out, "Error occurred: %s\n", ucl_parser_get_error(parser2));
+ fprintf (out, "%s\n", emitted);
+ ret = 1;
+ goto end;
+ }
+ if (emitted != NULL) {
+ free (emitted);
+ }
+ obj = ucl_parser_get_object (parser2);
+ if (json) {
+ emitted = ucl_object_emit (obj, UCL_EMIT_JSON);
+ }
+ else {
+ emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG);
+ }
+
+ fprintf (out, "%s\n", emitted);
+ ucl_object_unref (obj);
+
+end:
+ if (emitted != NULL) {
+ free (emitted);
+ }
+ if (parser2 != NULL) {
+ ucl_parser_free (parser2);
+ }
+ if (test_in != NULL) {
+ free (test_in);
+ }
+
+ fclose (out);
+
+ return ret;
+}
diff --git a/tests/test_generate.c b/tests/test_generate.c
new file mode 100644
index 000000000000..b2081ba512f1
--- /dev/null
+++ b/tests/test_generate.c
@@ -0,0 +1,126 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include "ucl.h"
+
+int
+main (int argc, char **argv)
+{
+ ucl_object_t *obj, *cur, *ar;
+ FILE *out;
+ unsigned char *emitted;
+ const char *fname_out = NULL;
+ int ret = 0;
+
+ switch (argc) {
+ case 2:
+ fname_out = argv[1];
+ break;
+ }
+
+
+ if (fname_out != NULL) {
+ out = fopen (fname_out, "w");
+ if (out == NULL) {
+ exit (-errno);
+ }
+ }
+ else {
+ out = stdout;
+ }
+
+ obj = ucl_object_typed_new (UCL_OBJECT);
+ /* Create some strings */
+ cur = ucl_object_fromstring_common (" test string ", 0, UCL_STRING_TRIM);
+ obj = ucl_object_insert_key (obj, cur, "key1", 0, false);
+ cur = ucl_object_fromstring_common (" test \nstring\n ", 0, UCL_STRING_TRIM | UCL_STRING_ESCAPE);
+ obj = ucl_object_insert_key (obj, cur, "key2", 0, false);
+ cur = ucl_object_fromstring_common (" test string \n", 0, 0);
+ obj = ucl_object_insert_key (obj, cur, "key3", 0, false);
+ /* Array of numbers */
+ cur = ucl_object_fromint (10);
+ ar = ucl_array_append (NULL, cur);
+ cur = ucl_object_fromdouble (10.1);
+ ar = ucl_array_append (ar, cur);
+ cur = ucl_object_fromdouble (9.999);
+ ar = ucl_array_prepend (ar, cur);
+
+ /* Removing from an array */
+ cur = ucl_object_fromdouble (1.0);
+ ar = ucl_array_append (ar, cur);
+ cur = ucl_array_delete (ar, cur);
+ assert (ucl_object_todouble (cur) == 1.0);
+ ucl_object_unref (cur);
+ cur = ucl_object_fromdouble (2.0);
+ ar = ucl_array_append (ar, cur);
+ cur = ucl_array_pop_last (ar);
+ assert (ucl_object_todouble (cur) == 2.0);
+ ucl_object_unref (cur);
+ cur = ucl_object_fromdouble (3.0);
+ ar = ucl_array_prepend (ar, cur);
+ cur = ucl_array_pop_first (ar);
+ assert (ucl_object_todouble (cur) == 3.0);
+ ucl_object_unref (cur);
+
+ obj = ucl_object_insert_key (obj, ar, "key4", 0, false);
+ cur = ucl_object_frombool (true);
+ obj = ucl_object_insert_key (obj, cur, "key4", 0, false);
+ /* Empty strings */
+ cur = ucl_object_fromstring_common (" ", 0, UCL_STRING_TRIM);
+ obj = ucl_object_insert_key (obj, cur, "key5", 0, false);
+ cur = ucl_object_fromstring_common ("", 0, UCL_STRING_ESCAPE);
+ obj = ucl_object_insert_key (obj, cur, "key6", 0, false);
+ cur = ucl_object_fromstring_common (" \n", 0, UCL_STRING_ESCAPE);
+ obj = ucl_object_insert_key (obj, cur, "key7", 0, false);
+ /* Numbers and booleans */
+ cur = ucl_object_fromstring_common ("1mb", 0, UCL_STRING_ESCAPE | UCL_STRING_PARSE);
+ obj = ucl_object_insert_key (obj, cur, "key8", 0, false);
+ cur = ucl_object_fromstring_common ("3.14", 0, UCL_STRING_PARSE);
+ obj = ucl_object_insert_key (obj, cur, "key9", 0, false);
+ cur = ucl_object_fromstring_common ("true", 0, UCL_STRING_PARSE);
+ obj = ucl_object_insert_key (obj, cur, "key10", 0, false);
+ cur = ucl_object_fromstring_common (" off ", 0, UCL_STRING_PARSE | UCL_STRING_TRIM);
+ obj = ucl_object_insert_key (obj, cur, "key11", 0, false);
+ cur = ucl_object_fromstring_common ("gslin@gslin.org", 0, UCL_STRING_PARSE_INT);
+ obj = ucl_object_insert_key (obj, cur, "key12", 0, false);
+ cur = ucl_object_fromstring_common ("#test", 0, UCL_STRING_PARSE_INT);
+ obj = ucl_object_insert_key (obj, cur, "key13", 0, false);
+ cur = ucl_object_frombool (true);
+ obj = ucl_object_insert_key (obj, cur, "k=3", 0, false);
+
+
+ emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG);
+
+ fprintf (out, "%s\n", emitted);
+ ucl_object_unref (obj);
+
+ if (emitted != NULL) {
+ free (emitted);
+ }
+ fclose (out);
+
+ return ret;
+}
diff --git a/tests/test_speed.c b/tests/test_speed.c
new file mode 100644
index 000000000000..cbf290c1f872
--- /dev/null
+++ b/tests/test_speed.c
@@ -0,0 +1,149 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+
+#ifdef __APPLE__
+#include <mach/mach_time.h>
+#endif
+
+#include "ucl.h"
+
+static double
+get_ticks (void)
+{
+ double res;
+
+#ifdef __APPLE__
+ res = mach_absolute_time () / 1000000000.;
+#else
+ struct timespec ts;
+ clock_gettime (CLOCK_MONOTONIC, &ts);
+
+ res = (double)ts.tv_sec + ts.tv_nsec / 1000000000.;
+#endif
+
+ return res;
+}
+
+int
+main (int argc, char **argv)
+{
+ void *map;
+ struct ucl_parser *parser;
+ ucl_object_t *obj;
+ int fin;
+ unsigned char *emitted;
+ struct stat st;
+ const char *fname_in = NULL;
+ int ret = 0;
+ double start, end, seconds;
+
+ switch (argc) {
+ case 2:
+ fname_in = argv[1];
+ break;
+ }
+
+ fin = open (fname_in, O_RDONLY);
+ if (fin == -1) {
+ perror ("open failed");
+ exit (EXIT_FAILURE);
+ }
+ parser = ucl_parser_new (UCL_PARSER_ZEROCOPY);
+
+ (void)fstat (fin, &st);
+ map = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fin, 0);
+ if (map == MAP_FAILED) {
+ perror ("mmap failed");
+ exit (EXIT_FAILURE);
+ }
+
+ close (fin);
+
+ start = get_ticks ();
+ ucl_parser_add_chunk (parser, map, st.st_size);
+
+ obj = ucl_parser_get_object (parser);
+ end = get_ticks ();
+
+ seconds = end - start;
+ printf ("ucl: parsed input in %.4f seconds\n", seconds);
+ if (ucl_parser_get_error(parser)) {
+ printf ("Error occurred: %s\n", ucl_parser_get_error(parser));
+ ret = 1;
+ goto err;
+ }
+
+ start = get_ticks ();
+ emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG);
+ end = get_ticks ();
+
+ seconds = end - start;
+ printf ("ucl: emitted config in %.4f seconds\n", seconds);
+
+ free (emitted);
+
+ start = get_ticks ();
+ emitted = ucl_object_emit (obj, UCL_EMIT_JSON);
+ end = get_ticks ();
+
+ seconds = end - start;
+ printf ("ucl: emitted json in %.4f seconds\n", seconds);
+
+ free (emitted);
+
+ start = get_ticks ();
+ emitted = ucl_object_emit (obj, UCL_EMIT_JSON_COMPACT);
+ end = get_ticks ();
+
+ seconds = end - start;
+ printf ("ucl: emitted compact json in %.4f seconds\n", seconds);
+
+ free (emitted);
+
+ start = get_ticks ();
+ emitted = ucl_object_emit (obj, UCL_EMIT_YAML);
+ end = get_ticks ();
+
+ seconds = end - start;
+ printf ("ucl: emitted yaml in %.4f seconds\n", seconds);
+
+ free (emitted);
+
+ ucl_parser_free (parser);
+ ucl_object_unref (obj);
+
+err:
+ munmap (map, st.st_size);
+
+ return ret;
+}
diff --git a/uthash/uthash.h b/uthash/uthash.h
new file mode 100644
index 000000000000..36b1cf46a4b4
--- /dev/null
+++ b/uthash/uthash.h
@@ -0,0 +1,720 @@
+/*
+Copyright (c) 2003-2013, Troy D. Hanson http://troydhanson.github.com/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTHASH_H
+#define UTHASH_H
+
+#include <string.h> /* memcmp,strlen */
+#include <stddef.h> /* ptrdiff_t */
+#include <stdlib.h> /* exit() */
+#include "xxhash.h"
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+ As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+ when compiling c++ source) this code uses whatever method is needed
+ or, for VS2008 where neither is available, uses casting workarounds. */
+#ifdef _MSC_VER /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */
+#define DECLTYPE(x) (decltype(x))
+#else /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#define DECLTYPE(x)
+#endif
+#else /* GNU, Sun and other compilers */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE_ASSIGN(dst,src) \
+do { \
+ char **_da_dst = (char**)(&(dst)); \
+ *_da_dst = (char*)(src); \
+} while(0)
+#else
+#define DECLTYPE_ASSIGN(dst,src) \
+do { \
+ (dst) = DECLTYPE(dst)(src); \
+} while(0)
+#endif
+
+/* a number of the hash function use uint32_t which isn't defined on win32 */
+#ifdef _MSC_VER
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#else
+#include <inttypes.h> /* uint32_t */
+#endif
+
+#define UTHASH_VERSION 1.9.8
+
+#ifndef uthash_fatal
+#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */
+#endif
+#ifndef uthash_malloc
+#define uthash_malloc(sz) malloc(sz) /* malloc fcn */
+#endif
+#ifndef uthash_free
+#define uthash_free(ptr,sz) free(ptr) /* free fcn */
+#endif
+
+#ifndef uthash_noexpand_fyi
+#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */
+#endif
+#ifndef uthash_expand_fyi
+#define uthash_expand_fyi(tbl) /* can be defined to log expands */
+#endif
+
+/* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS 32 /* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS_LOG2 5 /* lg2 of initial number of buckets */
+#define HASH_BKT_CAPACITY_THRESH 10 /* expand when bucket count reaches */
+
+/* calculate the element whose hash handle address is hhe */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+
+#define HASH_FIND(hh,head,keyptr,keylen,out) \
+do { \
+ unsigned _hf_bkt,_hf_hashv; \
+ out=NULL; \
+ if (head) { \
+ HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \
+ if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) { \
+ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \
+ keyptr,keylen,out); \
+ } \
+ } \
+} while (0)
+
+#ifdef HASH_BLOOM
+#define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM)
+#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0)
+#define HASH_BLOOM_MAKE(tbl) \
+do { \
+ (tbl)->bloom_nbits = HASH_BLOOM; \
+ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \
+ if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \
+ memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \
+ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \
+} while (0)
+
+#define HASH_BLOOM_FREE(tbl) \
+do { \
+ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \
+} while (0)
+
+#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8)))
+#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8)))
+
+#define HASH_BLOOM_ADD(tbl,hashv) \
+ HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1)))
+
+#define HASH_BLOOM_TEST(tbl,hashv) \
+ HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1)))
+
+#else
+#define HASH_BLOOM_MAKE(tbl)
+#define HASH_BLOOM_FREE(tbl)
+#define HASH_BLOOM_ADD(tbl,hashv)
+#define HASH_BLOOM_TEST(tbl,hashv) (1)
+#define HASH_BLOOM_BYTELEN 0
+#endif
+
+#define HASH_MAKE_TABLE(hh,head) \
+do { \
+ (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \
+ sizeof(UT_hash_table)); \
+ if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \
+ memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \
+ (head)->hh.tbl->tail = &((head)->hh); \
+ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \
+ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \
+ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \
+ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \
+ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \
+ if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \
+ memset((head)->hh.tbl->buckets, 0, \
+ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \
+ HASH_BLOOM_MAKE((head)->hh.tbl); \
+ (head)->hh.tbl->signature = HASH_SIGNATURE; \
+} while(0)
+
+#define HASH_ADD(hh,head,fieldname,keylen_in,add) \
+ HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add)
+
+#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \
+do { \
+ replaced=NULL; \
+ HASH_FIND(hh,head,&((add)->fieldname),keylen_in,replaced); \
+ if (replaced!=NULL) { \
+ HASH_DELETE(hh,head,replaced); \
+ }; \
+ HASH_ADD(hh,head,fieldname,keylen_in,add); \
+} while(0)
+
+#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \
+do { \
+ unsigned _ha_bkt; \
+ (add)->hh.next = NULL; \
+ (add)->hh.key = (const char*)keyptr; \
+ (add)->hh.keylen = (unsigned)keylen_in; \
+ if (!(head)) { \
+ head = (add); \
+ (head)->hh.prev = NULL; \
+ HASH_MAKE_TABLE(hh,head); \
+ } else { \
+ (head)->hh.tbl->tail->next = (add); \
+ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \
+ (head)->hh.tbl->tail = &((add)->hh); \
+ } \
+ (head)->hh.tbl->num_items++; \
+ (add)->hh.tbl = (head)->hh.tbl; \
+ HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \
+ (add)->hh.hashv, _ha_bkt); \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \
+ HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \
+ HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \
+ HASH_FSCK(hh,head); \
+} while(0)
+
+#define HASH_TO_BKT( hashv, num_bkts, bkt ) \
+do { \
+ bkt = ((hashv) & ((num_bkts) - 1)); \
+} while(0)
+
+/* delete "delptr" from the hash table.
+ * "the usual" patch-up process for the app-order doubly-linked-list.
+ * The use of _hd_hh_del below deserves special explanation.
+ * These used to be expressed using (delptr) but that led to a bug
+ * if someone used the same symbol for the head and deletee, like
+ * HASH_DELETE(hh,users,users);
+ * We want that to work, but by changing the head (users) below
+ * we were forfeiting our ability to further refer to the deletee (users)
+ * in the patch-up process. Solution: use scratch space to
+ * copy the deletee pointer, then the latter references are via that
+ * scratch pointer rather than through the repointed (users) symbol.
+ */
+#define HASH_DELETE(hh,head,delptr) \
+do { \
+ unsigned _hd_bkt; \
+ struct UT_hash_handle *_hd_hh_del; \
+ if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \
+ uthash_free((head)->hh.tbl->buckets, \
+ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \
+ HASH_BLOOM_FREE((head)->hh.tbl); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ head = NULL; \
+ } else { \
+ _hd_hh_del = &((delptr)->hh); \
+ if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \
+ (head)->hh.tbl->tail = \
+ (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \
+ (head)->hh.tbl->hho); \
+ } \
+ if ((delptr)->hh.prev) { \
+ ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \
+ (head)->hh.tbl->hho))->next = (delptr)->hh.next; \
+ } else { \
+ DECLTYPE_ASSIGN(head,(delptr)->hh.next); \
+ } \
+ if (_hd_hh_del->next) { \
+ ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next + \
+ (head)->hh.tbl->hho))->prev = \
+ _hd_hh_del->prev; \
+ } \
+ HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \
+ HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \
+ (head)->hh.tbl->num_items--; \
+ } \
+ HASH_FSCK(hh,head); \
+} while (0)
+
+
+/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
+#define HASH_FIND_STR(head,findstr,out) \
+ HASH_FIND(hh,head,findstr,strlen(findstr),out)
+#define HASH_ADD_STR(head,strfield,add) \
+ HASH_ADD(hh,head,strfield,strlen(add->strfield),add)
+#define HASH_REPLACE_STR(head,strfield,add,replaced) \
+ HASH_REPLACE(hh,head,strfield,strlen(add->strfield),add,replaced)
+#define HASH_FIND_INT(head,findint,out) \
+ HASH_FIND(hh,head,findint,sizeof(int),out)
+#define HASH_ADD_INT(head,intfield,add) \
+ HASH_ADD(hh,head,intfield,sizeof(int),add)
+#define HASH_REPLACE_INT(head,intfield,add,replaced) \
+ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)
+#define HASH_FIND_PTR(head,findptr,out) \
+ HASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define HASH_ADD_PTR(head,ptrfield,add) \
+ HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define HASH_REPLACE_PTR(head,ptrfield,add) \
+ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)
+#define HASH_DEL(head,delptr) \
+ HASH_DELETE(hh,head,delptr)
+
+/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.
+ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.
+ */
+#ifdef HASH_DEBUG
+#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0)
+#define HASH_FSCK(hh,head) \
+do { \
+ unsigned _bkt_i; \
+ unsigned _count, _bkt_count; \
+ char *_prev; \
+ struct UT_hash_handle *_thh; \
+ if (head) { \
+ _count = 0; \
+ for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \
+ _bkt_count = 0; \
+ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \
+ _prev = NULL; \
+ while (_thh) { \
+ if (_prev != (char*)(_thh->hh_prev)) { \
+ HASH_OOPS("invalid hh_prev %p, actual %p\n", \
+ _thh->hh_prev, _prev ); \
+ } \
+ _bkt_count++; \
+ _prev = (char*)(_thh); \
+ _thh = _thh->hh_next; \
+ } \
+ _count += _bkt_count; \
+ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \
+ HASH_OOPS("invalid bucket count %d, actual %d\n", \
+ (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \
+ } \
+ } \
+ if (_count != (head)->hh.tbl->num_items) { \
+ HASH_OOPS("invalid hh item count %d, actual %d\n", \
+ (head)->hh.tbl->num_items, _count ); \
+ } \
+ /* traverse hh in app order; check next/prev integrity, count */ \
+ _count = 0; \
+ _prev = NULL; \
+ _thh = &(head)->hh; \
+ while (_thh) { \
+ _count++; \
+ if (_prev !=(char*)(_thh->prev)) { \
+ HASH_OOPS("invalid prev %p, actual %p\n", \
+ _thh->prev, _prev ); \
+ } \
+ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \
+ _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \
+ (head)->hh.tbl->hho) : NULL ); \
+ } \
+ if (_count != (head)->hh.tbl->num_items) { \
+ HASH_OOPS("invalid app item count %d, actual %d\n", \
+ (head)->hh.tbl->num_items, _count ); \
+ } \
+ } \
+} while (0)
+#else
+#define HASH_FSCK(hh,head)
+#endif
+
+/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to
+ * the descriptor to which this macro is defined for tuning the hash function.
+ * The app can #include <unistd.h> to get the prototype for write(2). */
+#ifdef HASH_EMIT_KEYS
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \
+do { \
+ unsigned _klen = fieldlen; \
+ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \
+ write(HASH_EMIT_KEYS, keyptr, fieldlen); \
+} while (0)
+#else
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)
+#endif
+
+/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */
+#ifdef HASH_FUNCTION
+#define HASH_FCN HASH_FUNCTION
+#else
+#define HASH_FCN HASH_XX
+#endif
+
+#define XX_HASH_PRIME 2654435761U
+
+#define HASH_XX(key,keylen,num_bkts,hashv,bkt) \
+do { \
+ hashv = XXH32 (key, keylen, XX_HASH_PRIME); \
+ bkt = (hashv) & (num_bkts-1); \
+} while (0)
+
+
+
+/* key comparison function; return 0 if keys equal */
+#define HASH_KEYCMP(a,b,len) memcmp(a,b,len)
+
+/* iterate over items in a known bucket to find desired item */
+#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \
+do { \
+ if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); \
+ else out=NULL; \
+ while (out) { \
+ if ((out)->hh.keylen == keylen_in) { \
+ if ((HASH_KEYCMP((out)->hh.key,keyptr,keylen_in)) == 0) break; \
+ } \
+ if ((out)->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,(out)->hh.hh_next)); \
+ else out = NULL; \
+ } \
+} while(0)
+
+/* add an item to a bucket */
+#define HASH_ADD_TO_BKT(head,addhh) \
+do { \
+ head.count++; \
+ (addhh)->hh_next = head.hh_head; \
+ (addhh)->hh_prev = NULL; \
+ if (head.hh_head) { (head).hh_head->hh_prev = (addhh); } \
+ (head).hh_head=addhh; \
+ if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \
+ && (addhh)->tbl->noexpand != 1) { \
+ HASH_EXPAND_BUCKETS((addhh)->tbl); \
+ } \
+} while(0)
+
+/* remove an item from a given bucket */
+#define HASH_DEL_IN_BKT(hh,head,hh_del) \
+ (head).count--; \
+ if ((head).hh_head == hh_del) { \
+ (head).hh_head = hh_del->hh_next; \
+ } \
+ if (hh_del->hh_prev) { \
+ hh_del->hh_prev->hh_next = hh_del->hh_next; \
+ } \
+ if (hh_del->hh_next) { \
+ hh_del->hh_next->hh_prev = hh_del->hh_prev; \
+ }
+
+/* Bucket expansion has the effect of doubling the number of buckets
+ * and redistributing the items into the new buckets. Ideally the
+ * items will distribute more or less evenly into the new buckets
+ * (the extent to which this is true is a measure of the quality of
+ * the hash function as it applies to the key domain).
+ *
+ * With the items distributed into more buckets, the chain length
+ * (item count) in each bucket is reduced. Thus by expanding buckets
+ * the hash keeps a bound on the chain length. This bounded chain
+ * length is the essence of how a hash provides constant time lookup.
+ *
+ * The calculation of tbl->ideal_chain_maxlen below deserves some
+ * explanation. First, keep in mind that we're calculating the ideal
+ * maximum chain length based on the *new* (doubled) bucket count.
+ * In fractions this is just n/b (n=number of items,b=new num buckets).
+ * Since the ideal chain length is an integer, we want to calculate
+ * ceil(n/b). We don't depend on floating point arithmetic in this
+ * hash, so to calculate ceil(n/b) with integers we could write
+ *
+ * ceil(n/b) = (n/b) + ((n%b)?1:0)
+ *
+ * and in fact a previous version of this hash did just that.
+ * But now we have improved things a bit by recognizing that b is
+ * always a power of two. We keep its base 2 log handy (call it lb),
+ * so now we can write this with a bit shift and logical AND:
+ *
+ * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ *
+ */
+#define HASH_EXPAND_BUCKETS(tbl) \
+do { \
+ unsigned _he_bkt; \
+ unsigned _he_bkt_i; \
+ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \
+ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \
+ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \
+ 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \
+ if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \
+ memset(_he_new_buckets, 0, \
+ 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \
+ tbl->ideal_chain_maxlen = \
+ (tbl->num_items >> (tbl->log2_num_buckets+1)) + \
+ ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0); \
+ tbl->nonideal_items = 0; \
+ for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \
+ { \
+ _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \
+ while (_he_thh) { \
+ _he_hh_nxt = _he_thh->hh_next; \
+ HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt); \
+ _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \
+ if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \
+ tbl->nonideal_items++; \
+ _he_newbkt->expand_mult = _he_newbkt->count / \
+ tbl->ideal_chain_maxlen; \
+ } \
+ _he_thh->hh_prev = NULL; \
+ _he_thh->hh_next = _he_newbkt->hh_head; \
+ if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev = \
+ _he_thh; \
+ _he_newbkt->hh_head = _he_thh; \
+ _he_thh = _he_hh_nxt; \
+ } \
+ } \
+ uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \
+ tbl->num_buckets *= 2; \
+ tbl->log2_num_buckets++; \
+ tbl->buckets = _he_new_buckets; \
+ tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \
+ (tbl->ineff_expands+1) : 0; \
+ if (tbl->ineff_expands > 1) { \
+ tbl->noexpand=1; \
+ uthash_noexpand_fyi(tbl); \
+ } \
+ uthash_expand_fyi(tbl); \
+} while(0)
+
+
+/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
+/* Note that HASH_SORT assumes the hash handle name to be hh.
+ * HASH_SRT was added to allow the hash handle name to be passed in. */
+#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
+#define HASH_SRT(hh,head,cmpfcn) \
+do { \
+ unsigned _hs_i; \
+ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \
+ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \
+ if (head) { \
+ _hs_insize = 1; \
+ _hs_looping = 1; \
+ _hs_list = &((head)->hh); \
+ while (_hs_looping) { \
+ _hs_p = _hs_list; \
+ _hs_list = NULL; \
+ _hs_tail = NULL; \
+ _hs_nmerges = 0; \
+ while (_hs_p) { \
+ _hs_nmerges++; \
+ _hs_q = _hs_p; \
+ _hs_psize = 0; \
+ for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \
+ _hs_psize++; \
+ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \
+ ((void*)((char*)(_hs_q->next) + \
+ (head)->hh.tbl->hho)) : NULL); \
+ if (! (_hs_q) ) break; \
+ } \
+ _hs_qsize = _hs_insize; \
+ while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) { \
+ if (_hs_psize == 0) { \
+ _hs_e = _hs_q; \
+ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \
+ ((void*)((char*)(_hs_q->next) + \
+ (head)->hh.tbl->hho)) : NULL); \
+ _hs_qsize--; \
+ } else if ( (_hs_qsize == 0) || !(_hs_q) ) { \
+ _hs_e = _hs_p; \
+ if (_hs_p){ \
+ _hs_p = (UT_hash_handle*)((_hs_p->next) ? \
+ ((void*)((char*)(_hs_p->next) + \
+ (head)->hh.tbl->hho)) : NULL); \
+ } \
+ _hs_psize--; \
+ } else if (( \
+ cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \
+ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \
+ ) <= 0) { \
+ _hs_e = _hs_p; \
+ if (_hs_p){ \
+ _hs_p = (UT_hash_handle*)((_hs_p->next) ? \
+ ((void*)((char*)(_hs_p->next) + \
+ (head)->hh.tbl->hho)) : NULL); \
+ } \
+ _hs_psize--; \
+ } else { \
+ _hs_e = _hs_q; \
+ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \
+ ((void*)((char*)(_hs_q->next) + \
+ (head)->hh.tbl->hho)) : NULL); \
+ _hs_qsize--; \
+ } \
+ if ( _hs_tail ) { \
+ _hs_tail->next = ((_hs_e) ? \
+ ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \
+ } else { \
+ _hs_list = _hs_e; \
+ } \
+ if (_hs_e) { \
+ _hs_e->prev = ((_hs_tail) ? \
+ ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \
+ } \
+ _hs_tail = _hs_e; \
+ } \
+ _hs_p = _hs_q; \
+ } \
+ if (_hs_tail){ \
+ _hs_tail->next = NULL; \
+ } \
+ if ( _hs_nmerges <= 1 ) { \
+ _hs_looping=0; \
+ (head)->hh.tbl->tail = _hs_tail; \
+ DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \
+ } \
+ _hs_insize *= 2; \
+ } \
+ HASH_FSCK(hh,head); \
+ } \
+} while (0)
+
+/* This function selects items from one hash into another hash.
+ * The end result is that the selected items have dual presence
+ * in both hashes. There is no copy of the items made; rather
+ * they are added into the new hash through a secondary hash
+ * hash handle that must be present in the structure. */
+#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \
+do { \
+ unsigned _src_bkt, _dst_bkt; \
+ void *_last_elt=NULL, *_elt; \
+ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \
+ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \
+ if (src) { \
+ for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \
+ for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \
+ _src_hh; \
+ _src_hh = _src_hh->hh_next) { \
+ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \
+ if (cond(_elt)) { \
+ _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \
+ _dst_hh->key = _src_hh->key; \
+ _dst_hh->keylen = _src_hh->keylen; \
+ _dst_hh->hashv = _src_hh->hashv; \
+ _dst_hh->prev = _last_elt; \
+ _dst_hh->next = NULL; \
+ if (_last_elt_hh) { _last_elt_hh->next = _elt; } \
+ if (!dst) { \
+ DECLTYPE_ASSIGN(dst,_elt); \
+ HASH_MAKE_TABLE(hh_dst,dst); \
+ } else { \
+ _dst_hh->tbl = (dst)->hh_dst.tbl; \
+ } \
+ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \
+ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \
+ (dst)->hh_dst.tbl->num_items++; \
+ _last_elt = _elt; \
+ _last_elt_hh = _dst_hh; \
+ } \
+ } \
+ } \
+ } \
+ HASH_FSCK(hh_dst,dst); \
+} while (0)
+
+#define HASH_CLEAR(hh,head) \
+do { \
+ if (head) { \
+ uthash_free((head)->hh.tbl->buckets, \
+ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \
+ HASH_BLOOM_FREE((head)->hh.tbl); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head)=NULL; \
+ } \
+} while(0)
+
+#define HASH_OVERHEAD(hh,head) \
+ (size_t)((((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \
+ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \
+ (sizeof(UT_hash_table)) + \
+ (HASH_BLOOM_BYTELEN)))
+
+#ifdef NO_DECLTYPE
+#define HASH_ITER(hh,head,el,tmp) \
+for((el)=(head), (*(char**)(&(tmp)))=(char*)((head)?(head)->hh.next:NULL); \
+ el; (el)=(tmp),(*(char**)(&(tmp)))=(char*)((tmp)?(tmp)->hh.next:NULL))
+#else
+#define HASH_ITER(hh,head,el,tmp) \
+for((el)=(head),(tmp)=DECLTYPE(el)((head)?(head)->hh.next:NULL); \
+ el; (el)=(tmp),(tmp)=DECLTYPE(el)((tmp)?(tmp)->hh.next:NULL))
+#endif
+
+/* obtain a count of items in the hash */
+#define HASH_COUNT(head) HASH_CNT(hh,head)
+#define HASH_CNT(hh,head) ((head)?((head)->hh.tbl->num_items):0)
+
+typedef struct UT_hash_bucket {
+ struct UT_hash_handle *hh_head;
+ unsigned count;
+
+ /* expand_mult is normally set to 0. In this situation, the max chain length
+ * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If
+ * the bucket's chain exceeds this length, bucket expansion is triggered).
+ * However, setting expand_mult to a non-zero value delays bucket expansion
+ * (that would be triggered by additions to this particular bucket)
+ * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.
+ * (The multiplier is simply expand_mult+1). The whole idea of this
+ * multiplier is to reduce bucket expansions, since they are expensive, in
+ * situations where we know that a particular bucket tends to be overused.
+ * It is better to let its chain length grow to a longer yet-still-bounded
+ * value, than to do an O(n) bucket expansion too often.
+ */
+ unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* random signature used only to find hash tables in external analysis */
+#define HASH_SIGNATURE 0xa0111fe1
+#define HASH_BLOOM_SIGNATURE 0xb12220f2
+
+typedef struct UT_hash_table {
+ UT_hash_bucket *buckets;
+ unsigned num_buckets, log2_num_buckets;
+ unsigned num_items;
+ struct UT_hash_handle *tail; /* tail hh in app order, for fast append */
+ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
+
+ /* in an ideal situation (all buckets used equally), no bucket would have
+ * more than ceil(#items/#buckets) items. that's the ideal chain length. */
+ unsigned ideal_chain_maxlen;
+
+ /* nonideal_items is the number of items in the hash whose chain position
+ * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
+ * hash distribution; reaching them in a chain traversal takes >ideal steps */
+ unsigned nonideal_items;
+
+ /* ineffective expands occur when a bucket doubling was performed, but
+ * afterward, more than half the items in the hash had nonideal chain
+ * positions. If this happens on two consecutive expansions we inhibit any
+ * further expansion, as it's not helping; this happens when the hash
+ * function isn't a good fit for the key domain. When expansion is inhibited
+ * the hash will still work, albeit no longer in constant time. */
+ unsigned ineff_expands, noexpand;
+
+ uint32_t signature; /* used only to find hash tables in external analysis */
+#ifdef HASH_BLOOM
+ uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
+ uint8_t *bloom_bv;
+ char bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+ struct UT_hash_table *tbl;
+ void *prev; /* prev element in app order */
+ void *next; /* next element in app order */
+ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */
+ struct UT_hash_handle *hh_next; /* next hh in bucket order */
+ const void *key; /* ptr to enclosing struct's key */
+ unsigned keylen; /* enclosing struct's key len */
+ unsigned hashv; /* result of hash-fcn(key) */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */
diff --git a/uthash/utlist.h b/uthash/utlist.h
new file mode 100644
index 000000000000..c82dd916e2ed
--- /dev/null
+++ b/uthash/utlist.h
@@ -0,0 +1,757 @@
+/*
+Copyright (c) 2007-2013, Troy D. Hanson http://troydhanson.github.com/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTLIST_H
+#define UTLIST_H
+
+#define UTLIST_VERSION 1.9.8
+
+#include <assert.h>
+
+/*
+ * This file contains macros to manipulate singly and doubly-linked lists.
+ *
+ * 1. LL_ macros: singly-linked lists.
+ * 2. DL_ macros: doubly-linked lists.
+ * 3. CDL_ macros: circular doubly-linked lists.
+ *
+ * To use singly-linked lists, your structure must have a "next" pointer.
+ * To use doubly-linked lists, your structure must "prev" and "next" pointers.
+ * Either way, the pointer to the head of the list must be initialized to NULL.
+ *
+ * ----------------.EXAMPLE -------------------------
+ * struct item {
+ * int id;
+ * struct item *prev, *next;
+ * }
+ *
+ * struct item *list = NULL:
+ *
+ * int main() {
+ * struct item *item;
+ * ... allocate and populate item ...
+ * DL_APPEND(list, item);
+ * }
+ * --------------------------------------------------
+ *
+ * For doubly-linked lists, the append and delete macros are O(1)
+ * For singly-linked lists, append and delete are O(n) but prepend is O(1)
+ * The sort macro is O(n log(n)) for all types of single/double/circular lists.
+ */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+ As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+ when compiling c++ code), this code uses whatever method is needed
+ or, for VS2008 where neither is available, uses casting workarounds. */
+#ifdef _MSC_VER /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */
+#define LDECLTYPE(x) decltype(x)
+#else /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#define LDECLTYPE(x) char*
+#endif
+#elif defined(__ICCARM__)
+#define NO_DECLTYPE
+#define LDECLTYPE(x) char*
+#else /* GNU, Sun and other compilers */
+#define LDECLTYPE(x) __typeof(x)
+#endif
+
+/* for VS2008 we use some workarounds to get around the lack of decltype,
+ * namely, we always reassign our tmp variable to the list head if we need
+ * to dereference its prev/next pointers, and save/restore the real head.*/
+#ifdef NO_DECLTYPE
+#define _SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); }
+#define _NEXT(elt,list,next) ((char*)((list)->next))
+#define _NEXTASGN(elt,list,to,next) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); }
+/* #define _PREV(elt,list,prev) ((char*)((list)->prev)) */
+#define _PREVASGN(elt,list,to,prev) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); }
+#define _RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; }
+#define _CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); }
+#else
+#define _SV(elt,list)
+#define _NEXT(elt,list,next) ((elt)->next)
+#define _NEXTASGN(elt,list,to,next) ((elt)->next)=(to)
+/* #define _PREV(elt,list,prev) ((elt)->prev) */
+#define _PREVASGN(elt,list,to,prev) ((elt)->prev)=(to)
+#define _RS(list)
+#define _CASTASGN(a,b) (a)=(b)
+#endif
+
+/******************************************************************************
+ * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort *
+ * Unwieldy variable names used here to avoid shadowing passed-in variables. *
+ *****************************************************************************/
+#define LL_SORT(list, cmp) \
+ LL_SORT2(list, cmp, next)
+
+#define LL_SORT2(list, cmp, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ _CASTASGN(_ls_p,list); \
+ list = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list,next); _RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ } else { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ } \
+ if (_ls_tail) { \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \
+ } else { \
+ _CASTASGN(list,_ls_e); \
+ } \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ if (_ls_tail) { \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,NULL,next); _RS(list); \
+ } \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+
+#define DL_SORT(list, cmp) \
+ DL_SORT2(list, cmp, prev, next)
+
+#define DL_SORT2(list, cmp, prev, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ _CASTASGN(_ls_p,list); \
+ list = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list,next); _RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ } else { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ } \
+ if (_ls_tail) { \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \
+ } else { \
+ _CASTASGN(list,_ls_e); \
+ } \
+ _SV(_ls_e,list); _PREVASGN(_ls_e,list,_ls_tail,prev); _RS(list); \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ _CASTASGN(list->prev, _ls_tail); \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,NULL,next); _RS(list); \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+#define CDL_SORT(list, cmp) \
+ CDL_SORT2(list, cmp, prev, next)
+
+#define CDL_SORT2(list, cmp, prev, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ LDECLTYPE(list) _ls_oldhead; \
+ LDECLTYPE(list) _tmp; \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ _CASTASGN(_ls_p,list); \
+ _CASTASGN(_ls_oldhead,list); \
+ list = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ _SV(_ls_q,list); \
+ if (_NEXT(_ls_q,list,next) == _ls_oldhead) { \
+ _ls_q = NULL; \
+ } else { \
+ _ls_q = _NEXT(_ls_q,list,next); \
+ } \
+ _RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = \
+ _NEXT(_ls_p,list,next); _RS(list); _ls_psize--; \
+ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \
+ } else { \
+ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = \
+ _NEXT(_ls_q,list,next); _RS(list); _ls_qsize--; \
+ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \
+ } \
+ if (_ls_tail) { \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e,next); _RS(list); \
+ } else { \
+ _CASTASGN(list,_ls_e); \
+ } \
+ _SV(_ls_e,list); _PREVASGN(_ls_e,list,_ls_tail,prev); _RS(list); \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ _CASTASGN(list->prev,_ls_tail); \
+ _CASTASGN(_tmp,list); \
+ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_tmp,next); _RS(list); \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+/******************************************************************************
+ * singly linked list macros (non-circular) *
+ *****************************************************************************/
+#define LL_PREPEND(head,add) \
+ LL_PREPEND2(head,add,next)
+
+#define LL_PREPEND2(head,add,next) \
+do { \
+ (add)->next = head; \
+ head = add; \
+} while (0)
+
+#define LL_CONCAT(head1,head2) \
+ LL_CONCAT2(head1,head2,next)
+
+#define LL_CONCAT2(head1,head2,next) \
+do { \
+ LDECLTYPE(head1) _tmp; \
+ if (head1) { \
+ _tmp = head1; \
+ while (_tmp->next) { _tmp = _tmp->next; } \
+ _tmp->next=(head2); \
+ } else { \
+ (head1)=(head2); \
+ } \
+} while (0)
+
+#define LL_APPEND(head,add) \
+ LL_APPEND2(head,add,next)
+
+#define LL_APPEND2(head,add,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ (add)->next=NULL; \
+ if (head) { \
+ _tmp = head; \
+ while (_tmp->next) { _tmp = _tmp->next; } \
+ _tmp->next=(add); \
+ } else { \
+ (head)=(add); \
+ } \
+} while (0)
+
+#define LL_DELETE(head,del) \
+ LL_DELETE2(head,del,next)
+
+#define LL_DELETE2(head,del,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if ((head) == (del)) { \
+ (head)=(head)->next; \
+ } else { \
+ _tmp = head; \
+ while (_tmp->next && (_tmp->next != (del))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = ((del)->next); \
+ } \
+ } \
+} while (0)
+
+/* Here are VS2008 replacements for LL_APPEND and LL_DELETE */
+#define LL_APPEND_VS2008(head,add) \
+ LL_APPEND2_VS2008(head,add,next)
+
+#define LL_APPEND2_VS2008(head,add,next) \
+do { \
+ if (head) { \
+ (add)->next = head; /* use add->next as a temp variable */ \
+ while ((add)->next->next) { (add)->next = (add)->next->next; } \
+ (add)->next->next=(add); \
+ } else { \
+ (head)=(add); \
+ } \
+ (add)->next=NULL; \
+} while (0)
+
+#define LL_DELETE_VS2008(head,del) \
+ LL_DELETE2_VS2008(head,del,next)
+
+#define LL_DELETE2_VS2008(head,del,next) \
+do { \
+ if ((head) == (del)) { \
+ (head)=(head)->next; \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((head)->next && ((head)->next != (del))) { \
+ head = (head)->next; \
+ } \
+ if ((head)->next) { \
+ (head)->next = ((del)->next); \
+ } \
+ { \
+ char **_head_alias = (char**)&(head); \
+ *_head_alias = _tmp; \
+ } \
+ } \
+} while (0)
+#ifdef NO_DECLTYPE
+#undef LL_APPEND
+#define LL_APPEND LL_APPEND_VS2008
+#undef LL_DELETE
+#define LL_DELETE LL_DELETE_VS2008
+#undef LL_DELETE2
+#define LL_DELETE2 LL_DELETE2_VS2008
+#undef LL_APPEND2
+#define LL_APPEND2 LL_APPEND2_VS2008
+#undef LL_CONCAT /* no LL_CONCAT_VS2008 */
+#undef DL_CONCAT /* no DL_CONCAT_VS2008 */
+#endif
+/* end VS2008 replacements */
+
+#define LL_COUNT(head,el,counter) \
+ LL_COUNT2(head,el,counter,next) \
+
+#define LL_COUNT2(head,el,counter,next) \
+{ \
+ counter = 0; \
+ LL_FOREACH2(head,el,next){ ++counter; } \
+}
+
+#define LL_FOREACH(head,el) \
+ LL_FOREACH2(head,el,next)
+
+#define LL_FOREACH2(head,el,next) \
+ for(el=head;el;el=(el)->next)
+
+#define LL_FOREACH_SAFE(head,el,tmp) \
+ LL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define LL_FOREACH_SAFE2(head,el,tmp,next) \
+ for((el)=(head);(el) && (tmp = (el)->next, 1); (el) = tmp)
+
+#define LL_SEARCH_SCALAR(head,out,field,val) \
+ LL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define LL_SEARCH_SCALAR2(head,out,field,val,next) \
+do { \
+ LL_FOREACH2(head,out,next) { \
+ if ((out)->field == (val)) break; \
+ } \
+} while(0)
+
+#define LL_SEARCH(head,out,elt,cmp) \
+ LL_SEARCH2(head,out,elt,cmp,next)
+
+#define LL_SEARCH2(head,out,elt,cmp,next) \
+do { \
+ LL_FOREACH2(head,out,next) { \
+ if ((cmp(out,elt))==0) break; \
+ } \
+} while(0)
+
+#define LL_REPLACE_ELEM(head, el, add) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ (add)->next = (el)->next; \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ _tmp = head; \
+ while (_tmp->next && (_tmp->next != (el))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (add); \
+ } \
+ } \
+} while (0)
+
+#define LL_PREPEND_ELEM(head, el, add) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ (add)->next = (el); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ _tmp = head; \
+ while (_tmp->next && (_tmp->next != (el))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (add); \
+ } \
+ } \
+} while (0) \
+
+
+/******************************************************************************
+ * doubly linked list macros (non-circular) *
+ *****************************************************************************/
+#define DL_PREPEND(head,add) \
+ DL_PREPEND2(head,add,prev,next)
+
+#define DL_PREPEND2(head,add,prev,next) \
+do { \
+ (add)->next = head; \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (head)->prev = (add); \
+ } else { \
+ (add)->prev = (add); \
+ } \
+ (head) = (add); \
+} while (0)
+
+#define DL_APPEND(head,add) \
+ DL_APPEND2(head,add,prev,next)
+
+#define DL_APPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (head)->prev->next = (add); \
+ (head)->prev = (add); \
+ (add)->next = NULL; \
+ } else { \
+ (head)=(add); \
+ (head)->prev = (head); \
+ (head)->next = NULL; \
+ } \
+} while (0)
+
+#define DL_CONCAT(head1,head2) \
+ DL_CONCAT2(head1,head2,prev,next)
+
+#define DL_CONCAT2(head1,head2,prev,next) \
+do { \
+ LDECLTYPE(head1) _tmp; \
+ if (head2) { \
+ if (head1) { \
+ _tmp = (head2)->prev; \
+ (head2)->prev = (head1)->prev; \
+ (head1)->prev->next = (head2); \
+ (head1)->prev = _tmp; \
+ } else { \
+ (head1)=(head2); \
+ } \
+ } \
+} while (0)
+
+#define DL_DELETE(head,del) \
+ DL_DELETE2(head,del,prev,next)
+
+#define DL_DELETE2(head,del,prev,next) \
+do { \
+ assert((del)->prev != NULL); \
+ if ((del)->prev == (del)) { \
+ (head)=NULL; \
+ } else if ((del)==(head)) { \
+ (del)->next->prev = (del)->prev; \
+ (head) = (del)->next; \
+ } else { \
+ (del)->prev->next = (del)->next; \
+ if ((del)->next) { \
+ (del)->next->prev = (del)->prev; \
+ } else { \
+ (head)->prev = (del)->prev; \
+ } \
+ } \
+} while (0)
+
+#define DL_COUNT(head,el,counter) \
+ DL_COUNT2(head,el,counter,next) \
+
+#define DL_COUNT2(head,el,counter,next) \
+{ \
+ counter = 0; \
+ DL_FOREACH2(head,el,next){ ++counter; } \
+}
+
+#define DL_FOREACH(head,el) \
+ DL_FOREACH2(head,el,next)
+
+#define DL_FOREACH2(head,el,next) \
+ for(el=head;el;el=(el)->next)
+
+/* this version is safe for deleting the elements during iteration */
+#define DL_FOREACH_SAFE(head,el,tmp) \
+ DL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define DL_FOREACH_SAFE2(head,el,tmp,next) \
+ for((el)=(head);(el) && (tmp = (el)->next, 1); (el) = tmp)
+
+/* these are identical to their singly-linked list counterparts */
+#define DL_SEARCH_SCALAR LL_SEARCH_SCALAR
+#define DL_SEARCH LL_SEARCH
+#define DL_SEARCH_SCALAR2 LL_SEARCH_SCALAR2
+#define DL_SEARCH2 LL_SEARCH2
+
+#define DL_REPLACE_ELEM(head, el, add) \
+do { \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ (add)->next = (el)->next; \
+ if ((el)->next == NULL) { \
+ (add)->prev = (add); \
+ } else { \
+ (add)->prev = (el)->prev; \
+ (add)->next->prev = (add); \
+ } \
+ } else { \
+ (add)->next = (el)->next; \
+ (add)->prev = (el)->prev; \
+ (add)->prev->next = (add); \
+ if ((el)->next == NULL) { \
+ (head)->prev = (add); \
+ } else { \
+ (add)->next->prev = (add); \
+ } \
+ } \
+} while (0)
+
+#define DL_PREPEND_ELEM(head, el, add) \
+do { \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ (add)->next = (el); \
+ (add)->prev = (el)->prev; \
+ (el)->prev = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ (add)->prev->next = (add); \
+ } \
+} while (0) \
+
+
+/******************************************************************************
+ * circular doubly linked list macros *
+ *****************************************************************************/
+#define CDL_PREPEND(head,add) \
+ CDL_PREPEND2(head,add,prev,next)
+
+#define CDL_PREPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (head)->prev = (add); \
+ (add)->prev->next = (add); \
+ } else { \
+ (add)->prev = (add); \
+ (add)->next = (add); \
+ } \
+(head)=(add); \
+} while (0)
+
+#define CDL_DELETE(head,del) \
+ CDL_DELETE2(head,del,prev,next)
+
+#define CDL_DELETE2(head,del,prev,next) \
+do { \
+ if ( ((head)==(del)) && ((head)->next == (head))) { \
+ (head) = 0L; \
+ } else { \
+ (del)->next->prev = (del)->prev; \
+ (del)->prev->next = (del)->next; \
+ if ((del) == (head)) (head)=(del)->next; \
+ } \
+} while (0)
+
+#define CDL_COUNT(head,el,counter) \
+ CDL_COUNT2(head,el,counter,next) \
+
+#define CDL_COUNT2(head, el, counter,next) \
+{ \
+ counter = 0; \
+ CDL_FOREACH2(head,el,next){ ++counter; } \
+}
+
+#define CDL_FOREACH(head,el) \
+ CDL_FOREACH2(head,el,next)
+
+#define CDL_FOREACH2(head,el,next) \
+ for(el=head;el;el=((el)->next==head ? 0L : (el)->next))
+
+#define CDL_FOREACH_SAFE(head,el,tmp1,tmp2) \
+ CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next)
+
+#define CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next) \
+ for((el)=(head), ((tmp1)=(head)?((head)->prev):NULL); \
+ (el) && ((tmp2)=(el)->next, 1); \
+ ((el) = (((el)==(tmp1)) ? 0L : (tmp2))))
+
+#define CDL_SEARCH_SCALAR(head,out,field,val) \
+ CDL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define CDL_SEARCH_SCALAR2(head,out,field,val,next) \
+do { \
+ CDL_FOREACH2(head,out,next) { \
+ if ((out)->field == (val)) break; \
+ } \
+} while(0)
+
+#define CDL_SEARCH(head,out,elt,cmp) \
+ CDL_SEARCH2(head,out,elt,cmp,next)
+
+#define CDL_SEARCH2(head,out,elt,cmp,next) \
+do { \
+ CDL_FOREACH2(head,out,next) { \
+ if ((cmp(out,elt))==0) break; \
+ } \
+} while(0)
+
+#define CDL_REPLACE_ELEM(head, el, add) \
+do { \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ if ((el)->next == (el)) { \
+ (add)->next = (add); \
+ (add)->prev = (add); \
+ (head) = (add); \
+ } else { \
+ (add)->next = (el)->next; \
+ (add)->prev = (el)->prev; \
+ (add)->next->prev = (add); \
+ (add)->prev->next = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } \
+ } \
+} while (0)
+
+#define CDL_PREPEND_ELEM(head, el, add) \
+do { \
+ assert(head != NULL); \
+ assert(el != NULL); \
+ assert(add != NULL); \
+ (add)->next = (el); \
+ (add)->prev = (el)->prev; \
+ (el)->prev = (add); \
+ (add)->prev->next = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } \
+} while (0) \
+
+#endif /* UTLIST_H */
+
diff --git a/uthash/utstring.h b/uthash/utstring.h
new file mode 100644
index 000000000000..4ef7b5690dc6
--- /dev/null
+++ b/uthash/utstring.h
@@ -0,0 +1,410 @@
+/*
+Copyright (c) 2008-2013, Troy D. Hanson http://troydhanson.github.com/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* a dynamic string implementation using macros
+ */
+#ifndef UTSTRING_H
+#define UTSTRING_H
+
+#define UTSTRING_VERSION 1.9.8
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((__unused__))
+#else
+#define _UNUSED_
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#ifndef oom
+#define oom() exit(-1)
+#endif
+
+typedef struct {
+ char *d;
+ size_t n; /* allocd size */
+ size_t i; /* index of first unused byte */
+} UT_string;
+
+#define utstring_reserve(s,amt) \
+do { \
+ if (((s)->n - (s)->i) < (size_t)(amt)) { \
+ (s)->d = (char*)realloc((s)->d, (s)->n + amt); \
+ if ((s)->d == NULL) oom(); \
+ (s)->n += amt; \
+ } \
+} while(0)
+
+#define utstring_init(s) \
+do { \
+ (s)->n = 0; (s)->i = 0; (s)->d = NULL; \
+ utstring_reserve(s,128); \
+ (s)->d[0] = '\0'; \
+} while(0)
+
+#define utstring_done(s) \
+do { \
+ if ((s)->d != NULL) free((s)->d); \
+ (s)->n = 0; \
+} while(0)
+
+#define utstring_free(s) \
+do { \
+ utstring_done(s); \
+ free(s); \
+} while(0)
+
+#define utstring_new(s) \
+do { \
+ s = (UT_string*)calloc(sizeof(UT_string),1); \
+ if (!s) oom(); \
+ utstring_init(s); \
+} while(0)
+
+#define utstring_renew(s) \
+do { \
+ if (s) { \
+ utstring_clear(s); \
+ } else { \
+ utstring_new(s); \
+ } \
+} while(0)
+
+#define utstring_clear(s) \
+do { \
+ (s)->i = 0; \
+ (s)->d[0] = '\0'; \
+} while(0)
+
+#define utstring_bincpy(s,b,l) \
+do { \
+ utstring_reserve((s),(l)+1); \
+ if (l) memcpy(&(s)->d[(s)->i], b, l); \
+ (s)->i += l; \
+ (s)->d[(s)->i]='\0'; \
+} while(0)
+
+#define utstring_concat(dst,src) \
+do { \
+ utstring_reserve((dst),((src)->i)+1); \
+ if ((src)->i) memcpy(&(dst)->d[(dst)->i], (src)->d, (src)->i); \
+ (dst)->i += (src)->i; \
+ (dst)->d[(dst)->i]='\0'; \
+} while(0)
+
+#define utstring_len(s) ((unsigned)((s)->i))
+
+#define utstring_body(s) ((s)->d)
+
+_UNUSED_ static void utstring_printf_va(UT_string *s, const char *fmt, va_list ap) {
+ int n;
+ va_list cp;
+ while (1) {
+#ifdef _WIN32
+ cp = ap;
+#else
+ va_copy(cp, ap);
+#endif
+ n = vsnprintf (&s->d[s->i], s->n-s->i, fmt, cp);
+ va_end(cp);
+
+ if ((n > -1) && (n < (int)(s->n-s->i))) {
+ s->i += n;
+ return;
+ }
+
+ /* Else try again with more space. */
+ if (n > -1) utstring_reserve(s,n+1); /* exact */
+ else utstring_reserve(s,(s->n)*2); /* 2x */
+ }
+}
+#ifdef __GNUC__
+/* support printf format checking (2=the format string, 3=start of varargs) */
+static void utstring_printf(UT_string *s, const char *fmt, ...)
+ __attribute__ (( format( printf, 2, 3) ));
+#endif
+_UNUSED_ static void utstring_printf(UT_string *s, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap,fmt);
+ utstring_printf_va(s,fmt,ap);
+ va_end(ap);
+}
+
+#define utstring_append_len(dst, src, len) \
+do { \
+ while ((dst)->n-(dst)->i <= (len)) utstring_reserve((dst),((dst)->n)*2); \
+ memcpy(&(dst)->d[(dst)->i], (src), (len)); \
+ (dst)->i+=(len); \
+ (dst)->d[(dst)->i]='\0'; \
+} while(0)
+
+#define utstring_append_c(dst, c) \
+do { \
+ if ((dst)->n-(dst)->i < 2) utstring_reserve((dst),((dst)->n)*2); \
+ (dst)->d[(dst)->i++] = (c); \
+ (dst)->d[(dst)->i]='\0'; \
+} while(0)
+
+/*******************************************************************************
+ * begin substring search functions *
+ ******************************************************************************/
+/* Build KMP table from left to right. */
+_UNUSED_ static void _utstring_BuildTable(
+ const char *P_Needle,
+ ssize_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+
+ i = 0;
+ j = i - 1;
+ P_KMP_Table[i] = j;
+ while (i < P_NeedleLen)
+ {
+ while ( (j > -1) && (P_Needle[i] != P_Needle[j]) )
+ {
+ j = P_KMP_Table[j];
+ }
+ i++;
+ j++;
+ if (i < P_NeedleLen)
+ {
+ if (P_Needle[i] == P_Needle[j])
+ {
+ P_KMP_Table[i] = P_KMP_Table[j];
+ }
+ else
+ {
+ P_KMP_Table[i] = j;
+ }
+ }
+ else
+ {
+ P_KMP_Table[i] = j;
+ }
+ }
+
+ return;
+}
+
+
+/* Build KMP table from right to left. */
+_UNUSED_ static void _utstring_BuildTableR(
+ const char *P_Needle,
+ ssize_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+
+ i = P_NeedleLen - 1;
+ j = i + 1;
+ P_KMP_Table[i + 1] = j;
+ while (i >= 0)
+ {
+ while ( (j < P_NeedleLen) && (P_Needle[i] != P_Needle[j]) )
+ {
+ j = P_KMP_Table[j + 1];
+ }
+ i--;
+ j--;
+ if (i >= 0)
+ {
+ if (P_Needle[i] == P_Needle[j])
+ {
+ P_KMP_Table[i + 1] = P_KMP_Table[j + 1];
+ }
+ else
+ {
+ P_KMP_Table[i + 1] = j;
+ }
+ }
+ else
+ {
+ P_KMP_Table[i + 1] = j;
+ }
+ }
+
+ return;
+}
+
+
+/* Search data from left to right. ( Multiple search mode. ) */
+_UNUSED_ static long _utstring_find(
+ const char *P_Haystack,
+ size_t P_HaystackLen,
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+ long V_FindPosition = -1;
+
+ /* Search from left to right. */
+ i = j = 0;
+ while ( (j < (int)P_HaystackLen) && (((P_HaystackLen - j) + i) >= P_NeedleLen) )
+ {
+ while ( (i > -1) && (P_Needle[i] != P_Haystack[j]) )
+ {
+ i = P_KMP_Table[i];
+ }
+ i++;
+ j++;
+ if (i >= (int)P_NeedleLen)
+ {
+ /* Found. */
+ V_FindPosition = j - i;
+ break;
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from right to left. ( Multiple search mode. ) */
+_UNUSED_ static long _utstring_findR(
+ const char *P_Haystack,
+ size_t P_HaystackLen,
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+ long V_FindPosition = -1;
+
+ /* Search from right to left. */
+ j = (P_HaystackLen - 1);
+ i = (P_NeedleLen - 1);
+ while ( (j >= 0) && (j >= i) )
+ {
+ while ( (i < (int)P_NeedleLen) && (P_Needle[i] != P_Haystack[j]) )
+ {
+ i = P_KMP_Table[i + 1];
+ }
+ i--;
+ j--;
+ if (i < 0)
+ {
+ /* Found. */
+ V_FindPosition = j + 1;
+ break;
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from left to right. ( One time search mode. ) */
+_UNUSED_ static long utstring_find(
+ UT_string *s,
+ long P_StartPosition, /* Start from 0. -1 means last position. */
+ const char *P_Needle,
+ ssize_t P_NeedleLen)
+{
+ long V_StartPosition;
+ long V_HaystackLen;
+ long *V_KMP_Table;
+ long V_FindPosition = -1;
+
+ if (P_StartPosition < 0)
+ {
+ V_StartPosition = s->i + P_StartPosition;
+ }
+ else
+ {
+ V_StartPosition = P_StartPosition;
+ }
+ V_HaystackLen = s->i - V_StartPosition;
+ if ( (V_HaystackLen >= P_NeedleLen) && (P_NeedleLen > 0) )
+ {
+ V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
+ if (V_KMP_Table != NULL)
+ {
+ _utstring_BuildTable(P_Needle, P_NeedleLen, V_KMP_Table);
+
+ V_FindPosition = _utstring_find(s->d + V_StartPosition,
+ V_HaystackLen,
+ P_Needle,
+ P_NeedleLen,
+ V_KMP_Table);
+ if (V_FindPosition >= 0)
+ {
+ V_FindPosition += V_StartPosition;
+ }
+
+ free(V_KMP_Table);
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from right to left. ( One time search mode. ) */
+_UNUSED_ static long utstring_findR(
+ UT_string *s,
+ long P_StartPosition, /* Start from 0. -1 means last position. */
+ const char *P_Needle,
+ ssize_t P_NeedleLen)
+{
+ long V_StartPosition;
+ long V_HaystackLen;
+ long *V_KMP_Table;
+ long V_FindPosition = -1;
+
+ if (P_StartPosition < 0)
+ {
+ V_StartPosition = s->i + P_StartPosition;
+ }
+ else
+ {
+ V_StartPosition = P_StartPosition;
+ }
+ V_HaystackLen = V_StartPosition + 1;
+ if ( (V_HaystackLen >= P_NeedleLen) && (P_NeedleLen > 0) )
+ {
+ V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
+ if (V_KMP_Table != NULL)
+ {
+ _utstring_BuildTableR(P_Needle, P_NeedleLen, V_KMP_Table);
+
+ V_FindPosition = _utstring_findR(s->d,
+ V_HaystackLen,
+ P_Needle,
+ P_NeedleLen,
+ V_KMP_Table);
+
+ free(V_KMP_Table);
+ }
+ }
+
+ return V_FindPosition;
+}
+/*******************************************************************************
+ * end substring search functions *
+ ******************************************************************************/
+
+#endif /* UTSTRING_H */
diff --git a/utils/chargen.c b/utils/chargen.c
new file mode 100644
index 000000000000..d6fa86a20bd2
--- /dev/null
+++ b/utils/chargen.c
@@ -0,0 +1,128 @@
+/* Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file this utility generates character table for ucl
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdbool.h>
+
+static inline int
+print_flag (const char *flag, bool *need_or, char *val)
+{
+ int res;
+ res = sprintf (val, "%s%s", *need_or ? "|" : "", flag);
+
+ *need_or |= true;
+
+ return res;
+}
+
+int
+main (int argc, char **argv)
+{
+ int i, col, r;
+ const char *name = "ucl_chartable";
+ bool need_or;
+ char valbuf[2048];
+
+ col = 0;
+
+ if (argc > 1) {
+ name = argv[1];
+ }
+
+ printf ("static const unsigned int %s[255] = {\n", name);
+
+ for (i = 0; i < 255; i ++) {
+ need_or = false;
+ r = 0;
+ /* UCL_CHARACTER_VALUE_END */
+
+ if (i == ' ' || i == '\t') {
+ r += print_flag ("UCL_CHARACTER_WHITESPACE", &need_or, valbuf + r);
+ }
+ if (isspace (i)) {
+ r += print_flag ("UCL_CHARACTER_WHITESPACE_UNSAFE", &need_or, valbuf + r);
+ }
+ if (isalnum (i) || i >= 0x80 || i == '/' || i == '_') {
+ r += print_flag ("UCL_CHARACTER_KEY_START", &need_or, valbuf + r);
+ }
+ if (isalnum (i) || i == '-' || i == '_' || i == '/' || i == '.' || i >= 0x80) {
+ r += print_flag ("UCL_CHARACTER_KEY", &need_or, valbuf + r);
+ }
+ if (i == 0 || i == '\r' || i == '\n' || i == ']' || i == '}' || i == ';' || i == ',' || i == '#') {
+ r += print_flag ("UCL_CHARACTER_VALUE_END", &need_or, valbuf + r);
+ }
+ else {
+ if (isprint (i) || i >= 0x80) {
+ r += print_flag ("UCL_CHARACTER_VALUE_STR", &need_or, valbuf + r);
+ }
+ if (isdigit (i) || i == '-') {
+ r += print_flag ("UCL_CHARACTER_VALUE_DIGIT_START", &need_or, valbuf + r);
+ }
+ if (isalnum (i) || i == '.' || i == '-' || i == '+') {
+ r += print_flag ("UCL_CHARACTER_VALUE_DIGIT", &need_or, valbuf + r);
+ }
+ }
+ if (i == '"' || i == '\\' || i == '/' || i == 'b' ||
+ i == 'f' || i == 'n' || i == 'r' || i == 't' || i == 'u') {
+ r += print_flag ("UCL_CHARACTER_ESCAPE", &need_or, valbuf + r);
+ }
+ if (i == ' ' || i == '\t' || i == ':' || i == '=') {
+ r += print_flag ("UCL_CHARACTER_KEY_SEP", &need_or, valbuf + r);
+ }
+ if (i == '\n' || i == '\r' || i == '\\' || i == '\b' || i == '\t' ||
+ i == '"' || i == '\f') {
+ r += print_flag ("UCL_CHARACTER_JSON_UNSAFE", &need_or, valbuf + r);
+ }
+ if (i == '\n' || i == '\r' || i == '\\' || i == '\b' || i == '\t' ||
+ i == '"' || i == '\f' || i == '=' || i == ':' || i == '{' || i == '[' || i == ' ') {
+ r += print_flag ("UCL_CHARACTER_UCL_UNSAFE", &need_or, valbuf + r);
+ }
+
+ if (!need_or) {
+ r += print_flag ("UCL_CHARACTER_DENIED", &need_or, valbuf + r);
+ }
+
+ if (isprint (i)) {
+ r += sprintf (valbuf + r, " /* %c */", i);
+ }
+ if (i != 254) {
+ r += sprintf (valbuf + r, ", ");
+ }
+ col += r;
+ if (col > 80) {
+ printf ("\n%s", valbuf);
+ col = r;
+ }
+ else {
+ printf ("%s", valbuf);
+ }
+ }
+ printf ("\n}\n");
+
+ return 0;
+}
diff --git a/utils/objdump.c b/utils/objdump.c
new file mode 100644
index 000000000000..8bf6e28edd53
--- /dev/null
+++ b/utils/objdump.c
@@ -0,0 +1,159 @@
+/* Copyright (c) 2013, Dmitriy V. Reshetnikov
+ * Copyright (c) 2013, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "ucl.h"
+
+void
+ucl_obj_dump(ucl_object_t *obj, unsigned int shift)
+{
+ int num = shift * 4 + 5;
+ char *pre = (char *) malloc (num * sizeof(char));
+ ucl_object_t *cur, *tmp;
+ ucl_object_iter_t it = NULL, it_obj = NULL;
+
+ pre[--num] = 0x00;
+ while (num--)
+ pre[num] = 0x20;
+
+ tmp = obj;
+
+ while ((obj = ucl_iterate_object (tmp, &it, false))) {
+ printf ("%sucl object address: %p\n", pre + 4, obj);
+ if (obj->key != NULL) {
+ printf ("%skey: \"%s\"\n", pre, ucl_object_key (obj));
+ }
+ printf ("%sref: %hd\n", pre, obj->ref);
+ printf ("%slen: %u\n", pre, obj->len);
+ printf ("%sprev: %p\n", pre, obj->prev);
+ printf ("%snext: %p\n", pre, obj->next);
+ if (obj->type == UCL_OBJECT) {
+ printf ("%stype: UCL_OBJECT\n", pre);
+ printf ("%svalue: %p\n", pre, obj->value.ov);
+ while ((cur = ucl_iterate_object (obj, &it_obj, true))) {
+ ucl_obj_dump (cur, shift + 2);
+ }
+ }
+ else if (obj->type == UCL_ARRAY) {
+ printf ("%stype: UCL_ARRAY\n", pre);
+ printf ("%svalue: %p\n", pre, obj->value.av);
+ ucl_obj_dump (obj->value.av, shift + 2);
+ }
+ else if (obj->type == UCL_INT) {
+ printf ("%stype: UCL_INT\n", pre);
+ printf ("%svalue: %ld\n", pre, ucl_object_toint (obj));
+ }
+ else if (obj->type == UCL_FLOAT) {
+ printf ("%stype: UCL_FLOAT\n", pre);
+ printf ("%svalue: %f\n", pre, ucl_object_todouble (obj));
+ }
+ else if (obj->type == UCL_STRING) {
+ printf ("%stype: UCL_STRING\n", pre);
+ printf ("%svalue: \"%s\"\n", pre, ucl_object_tostring (obj));
+ }
+ else if (obj->type == UCL_BOOLEAN) {
+ printf ("%stype: UCL_BOOLEAN\n", pre);
+ printf ("%svalue: %s\n", pre, ucl_object_tostring_forced (obj));
+ }
+ else if (obj->type == UCL_TIME) {
+ printf ("%stype: UCL_TIME\n", pre);
+ printf ("%svalue: %f\n", pre, ucl_object_todouble (obj));
+ }
+ else if (obj->type == UCL_USERDATA) {
+ printf ("%stype: UCL_USERDATA\n", pre);
+ printf ("%svalue: %p\n", pre, obj->value.ud);
+ }
+ }
+
+ free (pre);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *fn = NULL;
+ char inbuf[8192];
+ struct ucl_parser *parser;
+ int k, ret = 0, r = 0;
+ ucl_object_t *obj = NULL;
+ ucl_object_t *par;
+ FILE *in;
+
+ if (argc > 1) {
+ fn = argv[1];
+ }
+
+ if (fn != NULL) {
+ in = fopen (fn, "r");
+ if (in == NULL) {
+ exit (-errno);
+ }
+ }
+ else {
+ in = stdin;
+ }
+
+ parser = ucl_parser_new (0);
+ while (!feof (in) && r < (int)sizeof (inbuf)) {
+ r += fread (inbuf + r, 1, sizeof (inbuf) - r, in);
+ }
+ ucl_parser_add_chunk (parser, inbuf, r);
+ fclose (in);
+ if (ucl_parser_get_error(parser)) {
+ printf ("Error occured: %s\n", ucl_parser_get_error(parser));
+ ret = 1;
+ goto end;
+ }
+
+ obj = ucl_parser_get_object (parser);
+ if (ucl_parser_get_error(parser)) {
+ printf ("Error occured: %s\n", ucl_parser_get_error(parser));
+ ret = 1;
+ goto end;
+ }
+
+ if (argc > 2) {
+ for (k = 2; k < argc; k++) {
+ printf ("search for \"%s\"... ", argv[k]);
+ par = ucl_object_find_key (obj, argv[k]);
+ printf ("%sfound\n", (par == NULL )?"not ":"");
+ ucl_obj_dump (par, 0);
+ }
+ }
+ else {
+ ucl_obj_dump (obj, 0);
+ }
+
+end:
+ if (parser != NULL) {
+ ucl_parser_free (parser);
+ }
+ if (obj != NULL) {
+ ucl_object_unref (obj);
+ }
+
+ return ret;
+}