aboutsummaryrefslogtreecommitdiffstats
path: root/src/ucl
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2013-10-22 17:04:20 +0100
committerVsevolod Stakhov <vsevolod@highsecure.ru>2013-10-22 17:04:20 +0100
commitb7a0873dac545a79778133152a9b6d9b4e47cc42 (patch)
treed389696b986920f8e8f66df01519f6f6d55a02b5 /src/ucl
parent41e269801406c374a041da0fd0c6b4eff6ba4f3d (diff)
downloadrspamd-b7a0873dac545a79778133152a9b6d9b4e47cc42.tar.gz
rspamd-b7a0873dac545a79778133152a9b6d9b4e47cc42.zip
Replace RCL to UCL from libucl to avoid duplicity in the code.
Diffstat (limited to 'src/ucl')
-rw-r--r--src/ucl/.gitignore3
-rw-r--r--src/ucl/CMakeLists.txt20
-rw-r--r--src/ucl/README.md236
-rw-r--r--src/ucl/include/ucl.h731
-rw-r--r--src/ucl/src/ucl_chartable.h266
-rw-r--r--src/ucl/src/ucl_emitter.c629
-rw-r--r--src/ucl/src/ucl_internal.h249
-rw-r--r--src/ucl/src/ucl_parser.c1420
-rw-r--r--src/ucl/src/ucl_util.c841
-rw-r--r--src/ucl/tests/1.in11
-rw-r--r--src/ucl/tests/1.res12
-rw-r--r--src/ucl/tests/2.in18
-rw-r--r--src/ucl/tests/2.res45
-rw-r--r--src/ucl/tests/3.in31
-rw-r--r--src/ucl/tests/3.res27
-rw-r--r--src/ucl/tests/4.in47
-rw-r--r--src/ucl/tests/4.res36
-rw-r--r--src/ucl/tests/generate.res18
-rw-r--r--src/ucl/tests/rcl_test.json.xzbin0 -> 1853872 bytes
-rwxr-xr-xsrc/ucl/tests/run_tests.sh57
-rw-r--r--src/ucl/tests/test_basic.c113
-rw-r--r--src/ucl/tests/test_generate.c98
-rw-r--r--src/ucl/tests/test_speed.c128
-rw-r--r--src/ucl/utils/chargen.c124
-rw-r--r--src/ucl/utils/objdump.c158
25 files changed, 5318 insertions, 0 deletions
diff --git a/src/ucl/.gitignore b/src/ucl/.gitignore
new file mode 100644
index 000000000..ea72388b3
--- /dev/null
+++ b/src/ucl/.gitignore
@@ -0,0 +1,3 @@
+.cproject
+.project
+.settings
diff --git a/src/ucl/CMakeLists.txt b/src/ucl/CMakeLists.txt
new file mode 100644
index 000000000..73937d199
--- /dev/null
+++ b/src/ucl/CMakeLists.txt
@@ -0,0 +1,20 @@
+SET(UCLSRC src/ucl_util.c
+ src/ucl_parser.c
+ src/ucl_emitter.c)
+
+ADD_LIBRARY(rspamd-ucl ${LINK_TYPE} ${UCLSRC})
+SET_TARGET_PROPERTIES(rspamd-ucl PROPERTIES VERSION ${RSPAMD_VERSION})
+SET_TARGET_PROPERTIES(rspamd-ucl PROPERTIES COMPILE_FLAGS "-DRSPAMD_LIB")
+
+IF(HAVE_FETCH_H)
+ TARGET_LINK_LIBRARIES(rspamd-ucl fetch)
+ELSE(HAVE_FETCH_H)
+ IF(CURL_FOUND)
+ TARGET_LINK_LIBRARIES(rspamd-ucl ${CURL_LIBRARIES})
+ ENDIF(CURL_FOUND)
+ENDIF(HAVE_FETCH_H)
+IF(NO_SHARED MATCHES "OFF")
+ INSTALL(TARGETS rspamd-ucl
+ LIBRARY DESTINATION ${LIBDIR}
+ PUBLIC_HEADER DESTINATION ${INCLUDEDIR})
+ENDIF(NO_SHARED MATCHES "OFF") \ No newline at end of file
diff --git a/src/ucl/README.md b/src/ucl/README.md
new file mode 100644
index 000000000..18779a835
--- /dev/null
+++ b/src/ucl/README.md
@@ -0,0 +1,236 @@
+## Introduction
+
+This document describes the main features and principles of the configuration
+language called `UCL` - universal configuration language.
+
+## 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"]
+}
+```
+
+### 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
+* 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;
+* `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.
+
+### 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/src/ucl/include/ucl.h b/src/ucl/include/ucl.h
new file mode 100644
index 000000000..1b8fa8631
--- /dev/null
+++ b/src/ucl/include/ucl.h
@@ -0,0 +1,731 @@
+/* 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 RCL_H_
+#define RCL_H_
+
+#include <string.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include "config.h"
+
+#include "uthash.h"
+#include "utlist.h"
+#include "utstring.h"
+
+/**
+ * @file rcl.h
+ * RCL is an rspamd configuration language, which is a form of
+ * JSON with less strict rules that make it more comfortable for
+ * using as a configuration language
+ */
+
+/**
+ * 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_object_todouble_safe ucl_obj_todouble_safe
+#define ucl_object_todouble ucl_obj_todouble
+#define ucl_object_tostring ucl_obj_tostring
+#define ucl_object_tostring_safe ucl_obj_tostring_safe
+#define ucl_object_tolstring ucl_obj_tolstring
+#define ucl_object_tolstring_safe ucl_obj_tolstring_safe
+#define ucl_object_toint ucl_obj_toint
+#define ucl_object_toint_safe ucl_obj_toint_safe
+#define ucl_object_toboolean ucl_obj_toboolean
+#define ucl_object_toboolean_safe ucl_obj_toboolean_safe
+#define ucl_object_find_key ucl_obj_get_key
+#define ucl_object_find_keyl ucl_obj_get_keyl
+#define ucl_object_unref ucl_obj_unref
+#define ucl_object_ref ucl_obj_ref
+#define ucl_object_free ucl_obj_free
+
+/**
+ * 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
+
+enum ucl_error {
+ UCL_EOK = 0, //!< UCL_EOK
+ UCL_ESYNTAX, //!< UCL_ESYNTAX
+ UCL_EIO, //!< UCL_EIO
+ UCL_ESTATE, //!< UCL_ESTATE
+ UCL_ENESTED, //!< UCL_ENESTED
+ UCL_EMACRO, //!< UCL_EMACRO
+ UCL_ERECURSION,//!< UCL_ERECURSION
+ UCL_EINTERNAL, //!< UCL_EINTERNAL
+ UCL_ESSL //!< UCL_ESSL
+};
+
+enum ucl_type {
+ UCL_OBJECT = 0,
+ UCL_ARRAY,
+ UCL_INT,
+ UCL_FLOAT,
+ UCL_STRING,
+ UCL_BOOLEAN,
+ UCL_TIME,
+ UCL_USERDATA
+};
+
+enum ucl_emitter {
+ UCL_EMIT_JSON = 0,
+ UCL_EMIT_JSON_COMPACT,
+ UCL_EMIT_CONFIG,
+ UCL_EMIT_YAML
+};
+
+enum ucl_flags {
+ UCL_FLAG_KEY_LOWERCASE = 0x1,
+ UCL_FLAG_ZEROCOPY = 0x2
+};
+
+typedef struct ucl_object_s {
+ 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 *ov; /**< array or hash */
+ void* ud; /**< opaque user data */
+ } value;
+ enum ucl_type type; /**< real type */
+ int ref; /**< reference count */
+ size_t len; /**< size 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 */
+ UT_hash_handle hh; /**< hash handle */
+} ucl_object_t;
+
+
+/**
+ * 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_object_t *new;
+ new = malloc (sizeof (ucl_object_t));
+ if (new != NULL) {
+ memset (new, 0, sizeof (ucl_object_t));
+ new->ref = 1;
+ }
+ return new;
+}
+
+/**
+ * String conversion flags
+ */
+enum ucl_string_flags {
+ UCL_STRING_ESCAPE = 0x1, /**< UCL_STRING_ESCAPE perform JSON escape */
+ UCL_STRING_TRIM = 0x2, /**< UCL_STRING_TRIM trim leading and trailing whitespaces */
+ UCL_STRING_PARSE_BOOLEAN = 0x4, /**< UCL_STRING_PARSE_BOOLEAN parse passed string and detect boolean */
+ UCL_STRING_PARSE_INT = 0x8, /**< UCL_STRING_PARSE_INT parse passed string and detect integer number */
+ UCL_STRING_PARSE_DOUBLE = 0x10, /**< UCL_STRING_PARSE_DOUBLE parse passed string and detect integer or float number */
+ UCL_STRING_PARSE_NUMBER = UCL_STRING_PARSE_INT|UCL_STRING_PARSE_DOUBLE , /**<
+ UCL_STRING_PARSE_NUMBER parse passed string and detect number */
+ UCL_STRING_PARSE = UCL_STRING_PARSE_BOOLEAN|UCL_STRING_PARSE_NUMBER /**<
+ UCL_STRING_PARSE parse passed string (and detect booleans and numbers) */
+};
+
+/**
+ * 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);
+
+/**
+ * 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
+ */
+static inline 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_object_t *found;
+
+ if (elt == NULL || key == NULL) {
+ return NULL;
+ }
+
+ if (top == NULL) {
+ top = ucl_object_new ();
+ top->type = UCL_OBJECT;
+ }
+ if (keylen == 0) {
+ keylen = strlen (key);
+ }
+
+ HASH_FIND (hh, top->value.ov, key, keylen, found);
+
+ if (!found) {
+ HASH_ADD_KEYPTR (hh, top->value.ov, key, keylen, elt);
+ }
+ DL_APPEND (found, elt);
+
+ if (copy_key) {
+ ucl_copy_key_trash (elt);
+ }
+
+ return top;
+}
+
+/**
+ * Append an element to the array object
+ * @param top destination object (will be created automatically if top is NULL)
+ * @param eltelement 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)
+{
+ if (elt == NULL) {
+ return NULL;
+ }
+
+ if (top == NULL) {
+ top = ucl_object_new ();
+ top->type = UCL_ARRAY;
+ }
+
+ DL_APPEND (top->value.ov, elt);
+
+ return 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)
+{
+ DL_APPEND (head, elt);
+ 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_obj_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_obj_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_obj_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_obj_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_obj_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_obj_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_obj_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_obj_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_obj_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 tostring as it
+ * allows zero-copy
+ * @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_obj_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_obj_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
+ */
+static inline ucl_object_t *
+ucl_obj_get_key (ucl_object_t *obj, const char *key)
+{
+ size_t keylen;
+ ucl_object_t *ret;
+
+ if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) {
+ return NULL;
+ }
+
+ keylen = strlen (key);
+ HASH_FIND (hh, obj->value.ov, key, keylen, ret);
+
+ return ret;
+}
+
+/**
+ * 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
+ */
+static inline ucl_object_t *
+ucl_obj_get_keyl (ucl_object_t *obj, const char *key, size_t klen)
+{
+ ucl_object_t *ret;
+
+ if (obj == NULL || obj->type != UCL_OBJECT || key == NULL) {
+ return NULL;
+ }
+
+ HASH_FIND (hh, obj->value.ov, key, klen, ret);
+
+ return ret;
+}
+
+/**
+ * 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->hh.keylen;
+ return obj->hh.key;
+}
+
+/**
+ * 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, UT_string **err);
+
+/* 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);
+
+/**
+ * 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, UT_string **err);
+
+/**
+ * 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,
+ UT_string **err);
+
+/**
+ * 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, UT_string **err);
+
+/**
+ * Free cl parser object
+ * @param parser parser object
+ */
+void ucl_parser_free (struct ucl_parser *parser);
+
+/**
+ * Free cl object
+ * @param obj cl object to free
+ */
+void ucl_obj_free (ucl_object_t *obj);
+
+/**
+ * Icrease reference count for an object
+ * @param obj object to ref
+ */
+static inline ucl_object_t *
+ucl_obj_ref (ucl_object_t *obj) {
+ obj->ref ++;
+ return obj;
+}
+
+/**
+ * Decrease reference count for an object
+ * @param obj object to unref
+ */
+static inline void
+ucl_obj_unref (ucl_object_t *obj) {
+ if (--obj->ref <= 0) {
+ ucl_obj_free (obj);
+ }
+}
+
+/**
+ * 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);
+
+/**
+ * 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, UT_string **err);
+
+#endif /* RCL_H_ */
diff --git a/src/ucl/src/ucl_chartable.h b/src/ucl/src/ucl_chartable.h
new file mode 100644
index 000000000..232043c1b
--- /dev/null
+++ b/src/ucl/src/ucl_chartable.h
@@ -0,0 +1,266 @@
+/* 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_WHITESPACE|UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_KEY_SEP|UCL_CHARACTER_JSON_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_END|UCL_CHARACTER_JSON_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_JSON_UNSAFE,
+UCL_CHARACTER_WHITESPACE_UNSAFE|UCL_CHARACTER_VALUE_END|UCL_CHARACTER_JSON_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_VALUE_STR /* ! */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_ESCAPE|UCL_CHARACTER_JSON_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_VALUE_END /* ; */, UCL_CHARACTER_VALUE_STR /* < */,
+UCL_CHARACTER_VALUE_STR|UCL_CHARACTER_KEY_SEP /* = */,
+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_VALUE_STR|UCL_CHARACTER_ESCAPE|UCL_CHARACTER_JSON_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_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/src/ucl_emitter.c b/src/ucl/src/ucl_emitter.c
new file mode 100644
index 000000000..78e321c53
--- /dev/null
+++ b/src/ucl/src/ucl_emitter.c
@@ -0,0 +1,629 @@
+/* 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 RCL object to the RCL format
+ */
+
+
+static void ucl_obj_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact);
+static void ucl_elt_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs,
+ bool start_tabs, bool is_top, bool expand_array);
+static void ucl_elt_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs,
+ bool start_tabs, bool compact, bool expand_array);
+
+/**
+ * Add tabulation to the output buffer
+ * @param buf target buffer
+ * @param tabs number of tabs to add
+ */
+static inline void
+ucl_add_tabs (UT_string *buf, unsigned int tabs, bool compact)
+{
+ char *p;
+ unsigned int i;
+
+ if (!compact) {
+ while (buf->n - buf->i <= tabs * 4) {
+ utstring_reserve (buf, buf->n * 2);
+ }
+ p = &buf->d[buf->i];
+ for (i = 0; i < tabs; i ++) {
+ memset (&p[i * 4], ' ', 4);
+ }
+ buf->i += i * 4;
+ buf->d[buf->i] = '\0';
+ }
+}
+
+/**
+ * Serialise string
+ * @param str string to emit
+ * @param buf target buffer
+ */
+static void
+ucl_elt_string_write_json (const char *str, size_t size, UT_string *buf)
+{
+ const char *p = str, *c = str;
+ size_t len = 0;
+
+ utstring_append_c (buf, '"');
+ while (size) {
+ if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
+ if (len > 0) {
+ utstring_append_len (buf, c, len);
+ }
+ switch (*p) {
+ case '\n':
+ utstring_append_len (buf, "\\n", 2);
+ break;
+ case '\r':
+ utstring_append_len (buf, "\\r", 2);
+ break;
+ case '\b':
+ utstring_append_len (buf, "\\b", 2);
+ break;
+ case '\t':
+ utstring_append_len (buf, "\\t", 2);
+ break;
+ case '\f':
+ utstring_append_len (buf, "\\f", 2);
+ break;
+ case '\\':
+ utstring_append_len (buf, "\\\\", 2);
+ break;
+ case '"':
+ utstring_append_len (buf, "\\\"", 2);
+ break;
+ }
+ len = 0;
+ c = ++p;
+ }
+ else {
+ p ++;
+ len ++;
+ }
+ size --;
+ }
+ if (len > 0) {
+ utstring_append_len (buf, c, len);
+ }
+ utstring_append_c (buf, '"');
+}
+
+static inline void
+ucl_print_float (UT_string *buf, double val)
+{
+ if (val == (double)(int)val) {
+ utstring_printf (buf, "%.1lf", val);
+ }
+ else if (fabs (val - (double)(int)val) < 0.0000001) {
+ /* Write at maximum precision */
+ utstring_printf (buf, "%.*lg", DBL_DIG, val);
+ }
+ else {
+ utstring_printf (buf, "%lf", val);
+ }
+}
+
+/**
+ * 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, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact)
+{
+ ucl_object_t *cur, *tmp;
+
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, compact);
+ }
+ if (compact) {
+ utstring_append_c (buf, '{');
+ }
+ else {
+ utstring_append_len (buf, "{\n", 2);
+ }
+ HASH_ITER (hh, obj, cur, tmp) {
+ ucl_add_tabs (buf, tabs + 1, compact);
+ if (cur->hh.keylen > 0) {
+ ucl_elt_string_write_json (cur->hh.key, cur->hh.keylen, buf);
+ }
+ else {
+ utstring_append_len (buf, "null", 4);
+ }
+ if (compact) {
+ utstring_append_c (buf, ':');
+ }
+ else {
+ utstring_append_len (buf, ": ", 2);
+ }
+ ucl_obj_write_json (cur, buf, tabs + 1, false, compact);
+ if (cur->hh.next != NULL) {
+ if (compact) {
+ utstring_append_c (buf, ',');
+ }
+ else {
+ utstring_append_len (buf, ",\n", 2);
+ }
+ }
+ else if (!compact) {
+ utstring_append_c (buf, '\n');
+ }
+ }
+ ucl_add_tabs (buf, tabs, compact);
+ utstring_append_c (buf, '}');
+}
+
+/**
+ * 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, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact)
+{
+ ucl_object_t *cur = obj;
+
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, compact);
+ }
+ if (compact) {
+ utstring_append_c (buf, '[');
+ }
+ else {
+ utstring_append_len (buf, "[\n", 2);
+ }
+ while (cur) {
+ ucl_elt_write_json (cur, buf, tabs + 1, true, compact);
+ if (cur->next != NULL) {
+ if (compact) {
+ utstring_append_c (buf, ',');
+ }
+ else {
+ utstring_append_len (buf, ",\n", 2);
+ }
+ }
+ else if (!compact) {
+ utstring_append_c (buf, '\n');
+ }
+ cur = cur->next;
+ }
+ ucl_add_tabs (buf, tabs, compact);
+ utstring_append_c (buf, ']');
+}
+
+/**
+ * Emit a single element
+ * @param obj object
+ * @param buf buffer
+ */
+void
+ucl_elt_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool compact)
+{
+ switch (obj->type) {
+ case UCL_INT:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, compact);
+ }
+ utstring_printf (buf, "%jd", (intmax_t)ucl_object_toint (obj));
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, compact);
+ }
+ ucl_print_float (buf, ucl_object_todouble (obj));
+ break;
+ case UCL_BOOLEAN:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, compact);
+ }
+ utstring_printf (buf, "%s", ucl_object_toboolean (obj) ? "true" : "false");
+ break;
+ case UCL_STRING:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, compact);
+ }
+ ucl_elt_string_write_json (obj->value.sv, obj->len, buf);
+ break;
+ case UCL_OBJECT:
+ ucl_elt_obj_write_json (obj->value.ov, buf, tabs, start_tabs, compact);
+ break;
+ case UCL_ARRAY:
+ ucl_elt_array_write_json (obj->value.ov, buf, 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, UT_string *buf, 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 (buf, tabs, compact);
+ }
+
+ if (compact) {
+ utstring_append_c (buf, '[');
+ }
+ else {
+ utstring_append_len (buf, "[\n", 2);
+ }
+ cur = obj;
+ while (cur != NULL) {
+ ucl_elt_write_json (cur, buf, tabs + 1, true, compact);
+ if (cur->next) {
+ utstring_append_c (buf, ',');
+ }
+ if (!compact) {
+ utstring_append_c (buf, '\n');
+ }
+ cur = cur->next;
+ }
+ ucl_add_tabs (buf, tabs, compact);
+ utstring_append_c (buf, ']');
+ }
+ else {
+ ucl_elt_write_json (obj, buf, tabs, start_tabs, compact);
+ }
+
+}
+
+/**
+ * Emit an object to json
+ * @param obj object
+ * @return json output (should be freed after using)
+ */
+static UT_string *
+ucl_object_emit_json (ucl_object_t *obj, bool compact)
+{
+ UT_string *buf;
+
+ /* Allocate large enough buffer */
+ utstring_new (buf);
+
+ ucl_obj_write_json (obj, buf, 0, false, compact);
+
+ return buf;
+}
+
+/**
+ * Write a single object to the buffer
+ * @param obj object to write
+ * @param buf target buffer
+ */
+static void
+ucl_elt_obj_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top)
+{
+ ucl_object_t *cur, *tmp;
+
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, is_top);
+ }
+ if (!is_top) {
+ utstring_append_len (buf, "{\n", 2);
+ }
+
+ HASH_ITER (hh, obj, cur, tmp) {
+ ucl_add_tabs (buf, tabs + 1, is_top);
+ utstring_append_len (buf, cur->hh.key, cur->hh.keylen);
+ if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) {
+ utstring_append_len (buf, " = ", 3);
+ }
+ else {
+ utstring_append_c (buf, ' ');
+ }
+ ucl_elt_write_rcl (cur, buf, is_top ? tabs : tabs + 1, false, false, true);
+ if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) {
+ utstring_append_len (buf, ";\n", 2);
+ }
+ else {
+ utstring_append_c (buf, '\n');
+ }
+ }
+
+ ucl_add_tabs (buf, tabs, is_top);
+ if (!is_top) {
+ utstring_append_c (buf, '}');
+ }
+}
+
+/**
+ * Write a single array to the buffer
+ * @param obj array to write
+ * @param buf target buffer
+ */
+static void
+ucl_elt_array_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top)
+{
+ ucl_object_t *cur = obj;
+
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+
+ utstring_append_len (buf, "[\n", 2);
+ while (cur) {
+ ucl_elt_write_rcl (cur, buf, tabs + 1, true, false, false);
+ utstring_append_len (buf, ",\n", 2);
+ cur = cur->next;
+ }
+ ucl_add_tabs (buf, tabs, false);
+ utstring_append_c (buf, ']');
+}
+
+/**
+ * Emit a single element
+ * @param obj object
+ * @param buf buffer
+ */
+static void
+ucl_elt_write_rcl (ucl_object_t *obj, UT_string *buf, unsigned int tabs,
+ bool start_tabs, bool is_top, bool expand_array)
+{
+ if (expand_array && obj->next != NULL) {
+ ucl_elt_array_write_rcl (obj, buf, tabs, start_tabs, is_top);
+ }
+ else {
+ switch (obj->type) {
+ case UCL_INT:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ utstring_printf (buf, "%jd", (intmax_t)ucl_object_toint (obj));
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ ucl_print_float (buf, ucl_object_todouble (obj));
+ break;
+ case UCL_BOOLEAN:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ utstring_printf (buf, "%s", ucl_object_toboolean (obj) ? "true" : "false");
+ break;
+ case UCL_STRING:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ ucl_elt_string_write_json (obj->value.sv, obj->len, buf);
+ break;
+ case UCL_OBJECT:
+ ucl_elt_obj_write_rcl (obj->value.ov, buf, tabs, start_tabs, is_top);
+ break;
+ case UCL_ARRAY:
+ ucl_elt_array_write_rcl (obj->value.ov, buf, 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 UT_string *
+ucl_object_emit_rcl (ucl_object_t *obj)
+{
+ UT_string *buf;
+
+ /* Allocate large enough buffer */
+ utstring_new (buf);
+
+ ucl_elt_write_rcl (obj, buf, 0, false, true, true);
+
+ return buf;
+}
+
+
+/**
+ * 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, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top)
+{
+ ucl_object_t *cur, *tmp;
+
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, is_top);
+ }
+ if (!is_top) {
+ utstring_append_len (buf, ": {\n", 4);
+ }
+
+ HASH_ITER (hh, obj, cur, tmp) {
+ ucl_add_tabs (buf, tabs + 1, is_top);
+ utstring_append_len (buf, cur->hh.key, cur->hh.keylen);
+ if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) {
+ utstring_append_len (buf, " : ", 3);
+ }
+ else {
+ utstring_append_c (buf, ' ');
+ }
+ ucl_elt_write_yaml (cur, buf, is_top ? tabs : tabs + 1, false, false, true);
+ if (cur->type != UCL_OBJECT && cur->type != UCL_ARRAY) {
+ if (!is_top) {
+ utstring_append_len (buf, ",\n", 2);
+ }
+ else {
+ utstring_append_c (buf, '\n');
+ }
+ }
+ else {
+ utstring_append_c (buf, '\n');
+ }
+ }
+
+ ucl_add_tabs (buf, tabs, is_top);
+ if (!is_top) {
+ utstring_append_c (buf, '}');
+ }
+}
+
+/**
+ * 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, UT_string *buf, unsigned int tabs, bool start_tabs, bool is_top)
+{
+ ucl_object_t *cur = obj;
+
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+
+ utstring_append_len (buf, ": [\n", 4);
+ while (cur) {
+ ucl_elt_write_yaml (cur, buf, tabs + 1, true, false, false);
+ utstring_append_len (buf, ",\n", 2);
+ cur = cur->next;
+ }
+ ucl_add_tabs (buf, tabs, false);
+ utstring_append_c (buf, ']');
+}
+
+/**
+ * Emit a single element
+ * @param obj object
+ * @param buf buffer
+ */
+static void
+ucl_elt_write_yaml (ucl_object_t *obj, UT_string *buf, unsigned int tabs,
+ bool start_tabs, bool is_top, bool expand_array)
+{
+ if (expand_array && obj->next != NULL) {
+ ucl_elt_array_write_yaml (obj, buf, tabs, start_tabs, is_top);
+ }
+ else {
+ switch (obj->type) {
+ case UCL_INT:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ utstring_printf (buf, "%jd", (intmax_t)ucl_object_toint (obj));
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ ucl_print_float (buf, ucl_object_todouble (obj));
+ break;
+ case UCL_BOOLEAN:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ utstring_printf (buf, "%s", ucl_object_toboolean (obj) ? "true" : "false");
+ break;
+ case UCL_STRING:
+ if (start_tabs) {
+ ucl_add_tabs (buf, tabs, false);
+ }
+ ucl_elt_string_write_json (obj->value.sv, obj->len, buf);
+ break;
+ case UCL_OBJECT:
+ ucl_elt_obj_write_yaml (obj->value.ov, buf, tabs, start_tabs, is_top);
+ break;
+ case UCL_ARRAY:
+ ucl_elt_array_write_yaml (obj->value.ov, buf, 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 UT_string *
+ucl_object_emit_yaml (ucl_object_t *obj)
+{
+ UT_string *buf;
+
+ /* Allocate large enough buffer */
+ utstring_new (buf);
+
+ ucl_elt_write_yaml (obj, buf, 0, false, true, true);
+
+ return buf;
+}
+
+unsigned char *
+ucl_object_emit (ucl_object_t *obj, enum ucl_emitter emit_type)
+{
+ UT_string *buf = NULL;
+ unsigned char *res = NULL;
+
+ if (emit_type == UCL_EMIT_JSON) {
+ buf = ucl_object_emit_json (obj, false);
+ }
+ else if (emit_type == UCL_EMIT_JSON_COMPACT) {
+ buf = ucl_object_emit_json (obj, true);
+ }
+ else if (emit_type == UCL_EMIT_YAML) {
+ buf = ucl_object_emit_yaml (obj);
+ }
+ else {
+ buf = ucl_object_emit_rcl (obj);
+ }
+
+ if (buf != NULL) {
+ res = utstring_body (buf);
+ free (buf);
+ }
+
+ return res;
+}
diff --git a/src/ucl/src/ucl_internal.h b/src/ucl/src/ucl_internal.h
new file mode 100644
index 000000000..97613c824
--- /dev/null
+++ b/src/ucl/src/ucl_internal.h
@@ -0,0 +1,249 @@
+/* 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 "ucl.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
+};
+
+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;
+};
+
+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_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;
+};
+
+/**
+ * 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, UT_string **err);
+
+/**
+ * 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, UT_string **err);
+
+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);
+
+
+void ucl_elt_write_json (ucl_object_t *obj, UT_string *buf, unsigned int tabs,
+ bool start_tabs, bool compact);
+
+#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 (tolower (p[0]) == 'f' && strncasecmp (p, "false", 5) == 0) {
+ ret = true;
+ val = false;
+ }
+ }
+ else if (len == 4) {
+ if (tolower (p[0]) == 't' && strncasecmp (p, "true", 4) == 0) {
+ ret = true;
+ val = true;
+ }
+ }
+ else if (len == 3) {
+ if (tolower (p[0]) == 'y' && strncasecmp (p, "yes", 3) == 0) {
+ ret = true;
+ val = true;
+ }
+ if (tolower (p[0]) == 'o' && strncasecmp (p, "off", 3) == 0) {
+ ret = true;
+ val = false;
+ }
+ }
+ else if (len == 2) {
+ if (tolower (p[0]) == 'n' && strncasecmp (p, "no", 2) == 0) {
+ ret = true;
+ val = false;
+ }
+ else if (tolower (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);
+
+#endif /* UCL_INTERNAL_H_ */
diff --git a/src/ucl/src/ucl_parser.c b/src/ucl/src/ucl_parser.c
new file mode 100644
index 000000000..30106ebd3
--- /dev/null
+++ b/src/ucl/src/ucl_parser.c
@@ -0,0 +1,1420 @@
+/* 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)
+{
+ ucl_create_err (err, "error on line %d at column %d: '%s', character: '%c'",
+ chunk->line, chunk->column, str, *chunk->pos);
+}
+
+static bool
+ucl_skip_comments (struct ucl_parser *parser, UT_string **err)
+{
+ 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, "comments nesting is invalid", 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;
+}
+
+static inline size_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, UT_string **err)
+{
+ size_t ret = 0;
+
+ if (need_unescape || need_lowercase || !(parser->flags & UCL_FLAG_ZEROCOPY)) {
+ /* Copy string */
+ *dst = UCL_ALLOC (in_len + 1);
+ if (*dst == NULL) {
+ ucl_set_err (parser->chunks, 0, "cannot allocate memory for a string", 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);
+ }
+ *dst_const = *dst;
+ }
+ else {
+ *dst_const = src;
+ ret = in_len;
+ }
+
+ return ret;
+}
+
+int
+ucl_maybe_parse_number (ucl_object_t *obj,
+ const char *start, const char *end, const char **pos, bool allow_double)
+{
+ const char *p = start, *c = start;
+ char *endptr;
+ bool got_dot = false, got_exp = false, need_double = false, is_date = false;
+ double dv;
+ int64_t lv;
+
+ if (*p == '-') {
+ p ++;
+ }
+ while (p < end) {
+ if (isdigit (*p)) {
+ p ++;
+ }
+ 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;
+ }
+ }
+
+ errno = 0;
+ if (need_double) {
+ dv = strtod (c, &endptr);
+ }
+ 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) {
+ 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 (p[1] == 'b' || p[1] == 'B') {
+ /* Megabytes */
+ 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, false);
+ }
+ 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, false);
+ }
+ 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 = dv;
+ }
+ else {
+ obj->type = UCL_INT;
+ obj->value.iv = lv;
+ }
+ *pos = p;
+ return 0;
+}
+
+/**
+ * Parse possible number
+ * @param parser
+ * @param chunk
+ * @param err
+ * @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, UT_string **err)
+{
+ const unsigned char *pos;
+ int ret;
+
+ ret = ucl_maybe_parse_number (obj, chunk->pos, chunk->end, (const char **)&pos, true);
+
+ 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", err);
+ }
+
+ return false;
+}
+
+/**
+ * Parse quoted string with possible escapes
+ * @param parser
+ * @param chunk
+ * @param err
+ * @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, UT_string **err)
+{
+ 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", err);
+ }
+ else {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unexpected control character", 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", 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", err);
+ return false;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+ if (p >= chunk->end) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", err);
+ return false;
+ }
+ }
+ else {
+ ucl_chunk_skipc (chunk, p);
+ }
+ }
+ else {
+ ucl_set_err (chunk, UCL_ESYNTAX, "invalid escape character", err);
+ return false;
+ }
+ *need_unescape = true;
+ continue;
+ }
+ else if (c == '"') {
+ ucl_chunk_skipc (chunk, p);
+ return true;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+
+ ucl_set_err (chunk, UCL_ESYNTAX, "no quote at the end of json string", err);
+ return false;
+}
+
+/**
+ * Parse a key in an object
+ * @param parser
+ * @param chunk
+ * @param err
+ * @return true if a key has been parsed
+ */
+static bool
+ucl_parse_key (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, UT_string **err)
+{
+ const unsigned char *p, *c = NULL, *end;
+ const char *key;
+ bool got_quote = false, got_eq = false, got_semicolon = false, need_unescape = false;
+ ucl_object_t *nobj, *tobj, *container;
+ size_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 (ucl_lex_is_comment (p[0], p[1])) {
+ if (!ucl_skip_comments (parser, err)) {
+ return false;
+ }
+ p = chunk->pos;
+ }
+ else if (ucl_test_character (*p, UCL_CHARACTER_KEY_START)) {
+ /* The first symbol */
+ c = p;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else if (*p == '"') {
+ /* JSON style key */
+ c = p + 1;
+ got_quote = true;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else {
+ /* Invalid identifier */
+ ucl_set_err (chunk, UCL_ESYNTAX, "key must begin with a letter", err);
+ return false;
+ }
+ }
+ else {
+ /* Parse the body of a key */
+ if (!got_quote) {
+ if (ucl_test_character (*p, UCL_CHARACTER_KEY)) {
+ 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", err);
+ return false;
+ }
+ }
+ else {
+ /* We need to parse json like quoted string */
+ if (!ucl_lex_json_string (parser, chunk, &need_unescape, err)) {
+ return false;
+ }
+ end = chunk->pos - 1;
+ p = chunk->pos;
+ break;
+ }
+ }
+ }
+
+ if (p >= chunk->end) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", err);
+ return 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", 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", err);
+ return false;
+ }
+ }
+ else if (ucl_lex_is_comment (p[0], p[1])) {
+ /* Check for comment */
+ if (!ucl_skip_comments (parser, err)) {
+ return false;
+ }
+ p = chunk->pos;
+ }
+ else {
+ /* Start value */
+ break;
+ }
+ }
+
+ if (p >= chunk->end) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", err);
+ return false;
+ }
+
+ /* 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_FLAG_KEY_LOWERCASE, err);
+ if (keylen == 0) {
+ return false;
+ }
+
+ container = parser->stack->obj->value.ov;
+ HASH_FIND (hh, container, key, keylen, tobj);
+ if (tobj == NULL) {
+ DL_APPEND (tobj, nobj);
+ HASH_ADD_KEYPTR (hh, container, key, keylen, nobj);
+ }
+ else {
+ DL_APPEND (tobj, nobj);
+ }
+
+ parser->stack->obj->value.ov = container;
+
+ parser->cur_obj = nobj;
+
+ return true;
+}
+
+/**
+ * Parse a cl string
+ * @param parser
+ * @param chunk
+ * @param err
+ * @return true if a key has been parsed
+ */
+static bool
+ucl_parse_string_value (struct ucl_parser *parser,
+ struct ucl_chunk *chunk, UT_string **err)
+{
+ 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;
+ }
+ }
+
+ if (ucl_lex_is_atom_end (*p) || 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", err);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse multiline string ending with \n{term}\n
+ * @param parser
+ * @param chunk
+ * @param term
+ * @param term_len
+ * @param err
+ * @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, UT_string **err)
+{
+ 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 {
+ newline = false;
+ }
+ ucl_chunk_skipc (chunk, p);
+ }
+
+ return len;
+}
+
+/**
+ * Handle value data
+ * @param parser
+ * @param chunk
+ * @param err
+ * @return
+ */
+static bool
+ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk, UT_string **err)
+{
+ const unsigned char *p, *c;
+ struct ucl_stack *st;
+ ucl_object_t *obj = NULL, *t;
+ unsigned int stripped_spaces;
+ int str_len;
+ bool need_unescape = 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.ov;
+ DL_APPEND (t, obj);
+ parser->cur_obj = obj;
+ parser->stack->obj->value.ov = t;
+ }
+ 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, err)) {
+ 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, err)) == 0) {
+ 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->type = UCL_OBJECT;
+
+ parser->state = UCL_STATE_KEY;
+ st = UCL_ALLOC (sizeof (struct ucl_stack));
+ st->obj = obj;
+ LL_PREPEND (parser->stack, st);
+ parser->cur_obj = obj;
+
+ ucl_chunk_skipc (chunk, p);
+ return true;
+ break;
+ case '[':
+ /* We have a new array */
+ obj = parser->cur_obj;
+ obj->type = UCL_ARRAY;
+
+ parser->state = UCL_STATE_VALUE;
+ st = UCL_ALLOC (sizeof (struct ucl_stack));
+ st->obj = obj;
+ LL_PREPEND (parser->stack, st);
+ parser->cur_obj = obj;
+
+ 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, err)) == 0) {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unterminated multiline value", 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, err)) == 0) {
+ 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) ||
+ 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, err)) {
+ 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, err)) {
+ if (parser->state == UCL_STATE_ERROR) {
+ return false;
+ }
+ if (!ucl_parse_string_value (parser, chunk, err)) {
+ return false;
+ }
+ if (!ucl_maybe_parse_boolean (obj, c, chunk->pos - c)) {
+ /* 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", 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, false, false, err)) == 0) {
+ return false;
+ }
+ obj->len = str_len;
+ }
+ parser->state = UCL_STATE_AFTER_VALUE;
+ return true;
+ }
+ else {
+ parser->state = UCL_STATE_AFTER_VALUE;
+ return true;
+ }
+ }
+ else {
+ if (!ucl_parse_string_value (parser, chunk, err)) {
+ return false;
+ }
+ if (!ucl_maybe_parse_boolean (obj, c, chunk->pos - c)) {
+ /* TODO: remove cut&paste */
+ /* 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", 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, false, false, err)) == 0) {
+ return false;
+ }
+ obj->len = str_len;
+ }
+ parser->state = UCL_STATE_AFTER_VALUE;
+ return true;
+ }
+ p = chunk->pos;
+ break;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Handle after value data
+ * @param parser
+ * @param chunk
+ * @param err
+ * @return
+ */
+static bool
+ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk, UT_string **err)
+{
+ 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 (ucl_lex_is_comment (p[0], p[1])) {
+ /* Skip comment */
+ if (!ucl_skip_comments (parser, err)) {
+ 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, "unexpected } detected", err);
+ return false;
+ }
+ if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) ||
+ (*p == ']' && parser->stack->obj->type == UCL_ARRAY)) {
+ /* Pop object from a stack */
+
+ st = parser->stack;
+ parser->stack = st->next;
+ UCL_FREE (sizeof (struct ucl_stack), st);
+ }
+ else {
+ ucl_set_err (chunk, UCL_ESYNTAX, "unexpected terminating symbol detected", 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", err);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Handle macro data
+ * @param parser
+ * @param chunk
+ * @param err
+ * @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, UT_string **err)
+{
+ const unsigned char *p, *c;
+ bool need_unescape = 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, err)) {
+ 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
+ * @param err if *err is NULL it is set to parser error
+ * @return true if chunk has been parsed and false in case of error
+ */
+static bool
+ucl_state_machine (struct ucl_parser *parser, UT_string **err)
+{
+ ucl_object_t *obj;
+ struct ucl_chunk *chunk = parser->chunks;
+ struct ucl_stack *st;
+ const unsigned char *p, *c, *macro_start = NULL;
+ size_t macro_len = 0;
+ struct ucl_macro *macro = NULL;
+
+ 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
+ */
+ if (!ucl_skip_comments (parser, err)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ else {
+ p = chunk->pos;
+ obj = ucl_object_new ();
+ if (*p == '[') {
+ parser->state = UCL_STATE_VALUE;
+ obj->type = UCL_ARRAY;
+ ucl_chunk_skipc (chunk, p);
+ }
+ else {
+ parser->state = UCL_STATE_KEY;
+ obj->type = UCL_OBJECT;
+ if (*p == '{') {
+ ucl_chunk_skipc (chunk, p);
+ }
+ };
+ parser->cur_obj = obj;
+ parser->top_obj = obj;
+ st = UCL_ALLOC (sizeof (struct ucl_stack));
+ st->obj = obj;
+ LL_PREPEND (parser->stack, st);
+ }
+ 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 (!ucl_parse_key (parser, chunk, err)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ if (parser->state != UCL_STATE_MACRO_NAME) {
+ 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, err)) {
+ 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, err)) {
+ 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 */
+ HASH_FIND (hh, parser->macroes, c, (p - c), macro);
+ if (macro == NULL) {
+ ucl_set_err (chunk, UCL_EMACRO, "unknown macro", err);
+ 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 (ucl_lex_is_comment (p[0], p[1])) {
+ /* Skip comment */
+ if (!ucl_skip_comments (parser, err)) {
+ 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, err)) {
+ parser->prev_state = parser->state;
+ parser->state = UCL_STATE_ERROR;
+ return false;
+ }
+ parser->state = parser->prev_state;
+ if (!macro->handler (macro_start, macro_len, macro->ud, err)) {
+ return false;
+ }
+ p = chunk->pos;
+ break;
+ default:
+ /* TODO: add all states */
+ ucl_set_err (chunk, UCL_EMACRO, "internal error: parser is in an unknown state", 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, "includes", ucl_includes_handler, new);
+
+ new->flags = flags;
+
+ 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);
+}
+
+bool
+ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
+ size_t len, UT_string **err)
+{
+ 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 (err, "maximum include nesting limit is reached: %d",
+ parser->recursion);
+ return false;
+ }
+ return ucl_state_machine (parser, err);
+ }
+
+ ucl_create_err (err, "a parser is in an invalid state");
+
+ return false;
+}
diff --git a/src/ucl/src/ucl_util.c b/src/ucl/src/ucl_util.c
new file mode 100644
index 000000000..510035c55
--- /dev/null
+++ b/src/ucl/src/ucl_util.c
@@ -0,0 +1,841 @@
+/* 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"
+
+#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.ov;
+ while (sub != NULL) {
+ tmp = sub->next;
+ ucl_object_free_internal (sub, false);
+ sub = tmp;
+ }
+ }
+ else if (obj->type == UCL_OBJECT) {
+ HASH_ITER (hh, obj->value.ov, sub, tmp) {
+ HASH_DELETE (hh, obj->value.ov, sub);
+ ucl_object_free_internal (sub, true);
+ }
+ }
+ tmp = obj->next;
+ UCL_FREE (sizeof (ucl_object_t), obj);
+ obj = tmp;
+
+ if (!allow_rec) {
+ break;
+ }
+ }
+}
+
+void
+ucl_obj_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++ = '?';
+ 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->hh.key != NULL) {
+ obj->trash_stack[UCL_TRASH_KEY] = malloc (obj->hh.keylen + 1);
+ if (obj->trash_stack[UCL_TRASH_KEY] != NULL) {
+ memcpy (obj->trash_stack[UCL_TRASH_KEY], obj->hh.key, obj->hh.keylen);
+ obj->trash_stack[UCL_TRASH_KEY][obj->hh.keylen] = '\0';
+ }
+ obj->hh.key = obj->trash_stack[UCL_TRASH_KEY];
+ }
+
+ return obj->trash_stack[UCL_TRASH_KEY];
+}
+
+char *
+ucl_copy_value_trash (ucl_object_t *obj)
+{
+ UT_string *emitted;
+ 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';
+ }
+ }
+ else {
+ /* Just emit value in json notation */
+ utstring_new (emitted);
+
+ if (emitted != NULL) {
+ ucl_elt_write_json (obj, emitted, 0, 0, true);
+ obj->trash_stack[UCL_TRASH_VALUE] = emitted->d;
+ obj->len = emitted->i;
+ free (emitted);
+ }
+ }
+ }
+ return obj->trash_stack[UCL_TRASH_VALUE];
+}
+
+ucl_object_t*
+ucl_parser_get_object (struct ucl_parser *parser, UT_string **err)
+{
+ if (parser->state != UCL_STATE_INIT && parser->state != UCL_STATE_ERROR) {
+ 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;
+
+ 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);
+ }
+
+ UCL_FREE (sizeof (struct ucl_parser), parser);
+}
+
+bool
+ucl_pubkey_add (struct ucl_parser *parser, const unsigned char *key, size_t len, UT_string **err)
+{
+ struct ucl_pubkey *nkey;
+#ifndef HAVE_OPENSSL
+ ucl_create_err (err, "cannot check signatures without openssl");
+ return false;
+#else
+# if (OPENSSL_VERSION_NUMBER < 0x10000000L)
+ ucl_create_err (err, "cannot check signatures, openssl version is unsupported");
+ return EXIT_FAILURE;
+# else
+ 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 (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 = g_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)
+{
+
+#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) {
+ ucl_create_err (err, "cannot fetch URL %s: %s",
+ url, strerror (errno));
+ fetchFreeURL (fetch_url);
+ return false;
+ }
+
+ *buflen = us.size;
+ *buf = g_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) {
+ ucl_create_err (err, "error fetching URL %s: %s",
+ url, curl_easy_strerror (r));
+ curl_easy_cleanup (curl);
+ if (buf != NULL) {
+ free (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)
+{
+ int fd;
+ struct stat st;
+
+ if (stat (filename, &st) == -1) {
+ ucl_create_err (err, "cannot stat file %s: %s",
+ filename, strerror (errno));
+ return false;
+ }
+ 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, UT_string **err)
+{
+
+ bool res;
+ unsigned char *buf = NULL, *sigbuf = NULL;
+ size_t buflen = 0, siglen = 0;
+ struct ucl_chunk *chunk;
+ char urlbuf[PATH_MAX];
+
+ snprintf (urlbuf, sizeof (urlbuf), "%.*s", (int)len, data);
+
+ if (!ucl_fetch_url (urlbuf, &buf, &buflen, err)) {
+ return false;
+ }
+
+ if (check_signature) {
+#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L)
+ /* We need to check signature first */
+ snprintf (urlbuf, sizeof (urlbuf), "%.*s.sig", (int)len, data);
+ if (!ucl_fetch_file (urlbuf, &sigbuf, &siglen, err)) {
+ return false;
+ }
+ if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) {
+ ucl_create_err (err, "cannot verify url %s: %s",
+ urlbuf,
+ ERR_error_string (ERR_get_error (), NULL));
+ munmap (sigbuf, siglen);
+ return false;
+ }
+ munmap (sigbuf, siglen);
+#endif
+ }
+
+ res = ucl_parser_add_chunk (parser, buf, buflen, err);
+ 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);
+ }
+ }
+ 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, UT_string **err)
+{
+ bool res;
+ struct ucl_chunk *chunk;
+ unsigned char *buf = NULL, *sigbuf = NULL;
+ size_t buflen, siglen;
+ char filebuf[PATH_MAX], realbuf[PATH_MAX];
+
+ snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data);
+ if (realpath (filebuf, realbuf) == NULL) {
+ ucl_create_err (err, "cannot open file %s: %s",
+ filebuf,
+ strerror (errno));
+ return false;
+ }
+
+ if (!ucl_fetch_file (realbuf, &buf, &buflen, err)) {
+ return false;
+ }
+
+ if (check_signature) {
+#if (defined(HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10000000L)
+ /* We need to check signature first */
+ snprintf (filebuf, sizeof (filebuf), "%s.sig", realbuf);
+ if (!ucl_fetch_file (filebuf, &sigbuf, &siglen, err)) {
+ return false;
+ }
+ if (!ucl_sig_check (buf, buflen, sigbuf, siglen, parser)) {
+ ucl_create_err (err, "cannot verify file %s: %s",
+ filebuf,
+ ERR_error_string (ERR_get_error (), NULL));
+ munmap (sigbuf, siglen);
+ return false;
+ }
+ munmap (sigbuf, siglen);
+#endif
+ }
+
+ res = ucl_parser_add_chunk (parser, buf, buflen, err);
+ 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);
+ }
+ }
+ 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, UT_string **err)
+{
+ struct ucl_parser *parser = ud;
+
+ if (*data == '/' || *data == '.') {
+ /* Try to load a file */
+ return ucl_include_file (data, len, parser, false, err);
+ }
+
+ return ucl_include_url (data, len, parser, false, err);
+}
+
+/**
+ * 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, UT_string **err)
+{
+ struct ucl_parser *parser = ud;
+
+ if (*data == '/' || *data == '.') {
+ /* Try to load a file */
+ return ucl_include_file (data, len, parser, true, err);
+ }
+
+ return ucl_include_url (data, len, parser, true, err);
+}
+
+bool
+ucl_parser_add_file (struct ucl_parser *parser, const char *filename,
+ UT_string **err)
+{
+ unsigned char *buf;
+ size_t len;
+ bool ret;
+
+ if (!ucl_fetch_file (filename, &buf, &len, err)) {
+ return false;
+ }
+
+ ret = ucl_parser_add_chunk (parser, buf, len, err);
+
+ 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);
+ }
+ }
+ else {
+ ucl_maybe_parse_number (obj, dst, dst + obj->len, &pos,
+ flags & UCL_STRING_PARSE_DOUBLE);
+ }
+ }
+ }
+
+ return obj;
+}
diff --git a/src/ucl/tests/1.in b/src/ucl/tests/1.in
new file mode 100644
index 000000000..41b0cfb3c
--- /dev/null
+++ b/src/ucl/tests/1.in
@@ -0,0 +1,11 @@
+{
+"key1": value;
+"key1": value2;
+"key1": "value;"
+"key1": 1.0,
+"key1": -1e-10,
+"key1": 1
+"key1": true
+"key1": no
+"key1": yes
+}
diff --git a/src/ucl/tests/1.res b/src/ucl/tests/1.res
new file mode 100644
index 000000000..789c82769
--- /dev/null
+++ b/src/ucl/tests/1.res
@@ -0,0 +1,12 @@
+key1 [
+ "value",
+ "value2",
+ "value;",
+ 1.0,
+ -1e-10,
+ 1,
+ true,
+ false,
+ true,
+]
+
diff --git a/src/ucl/tests/2.in b/src/ucl/tests/2.in
new file mode 100644
index 000000000..59a4d0c38
--- /dev/null
+++ b/src/ucl/tests/2.in
@@ -0,0 +1,18 @@
+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"
diff --git a/src/ucl/tests/2.res b/src/ucl/tests/2.res
new file mode 100644
index 000000000..3a8966f65
--- /dev/null
+++ b/src/ucl/tests/2.res
@@ -0,0 +1,45 @@
+section1 {
+ param1 = "value";
+ param2 = "value";
+ section3 {
+ param = "value";
+ param2 = "value";
+ param3 [
+ "value1",
+ "value2",
+ 100500,
+ ]
+ }
+}
+section2 {
+ param1 [
+ {
+ key = "value";
+ },
+ [
+ "key",
+ ],
+ ]
+}
+key1 [
+ 1.0,
+ "some string",
+]
+key2 [
+ 60.0,
+ "/some/path",
+]
+key3 [
+ 1024,
+ "111some",
+]
+key4 [
+ 5000000,
+ "s1",
+]
+key5 [
+ 0.010000,
+ "\n\r123",
+]
+key6 = 2207520000.000000;
+
diff --git a/src/ucl/tests/3.in b/src/ucl/tests/3.in
new file mode 100644
index 000000000..b3e369670
--- /dev/null
+++ b/src/ucl/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/src/ucl/tests/3.res b/src/ucl/tests/3.res
new file mode 100644
index 000000000..2f84ed69e
--- /dev/null
+++ b/src/ucl/tests/3.res
@@ -0,0 +1,27 @@
+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'",
+ "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/src/ucl/tests/4.in b/src/ucl/tests/4.in
new file mode 100644
index 000000000..2b296efff
--- /dev/null
+++ b/src/ucl/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/src/ucl/tests/4.res b/src/ucl/tests/4.res
new file mode 100644
index 000000000..58c359943
--- /dev/null
+++ b/src/ucl/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/src/ucl/tests/generate.res b/src/ucl/tests/generate.res
new file mode 100644
index 000000000..e7659ec3a
--- /dev/null
+++ b/src/ucl/tests/generate.res
@@ -0,0 +1,18 @@
+key1 = "test string";
+key2 = "test \\nstring";
+key3 = " test string \n";
+key4 [
+ [
+ 10,
+ 10.100000,
+ ],
+ true,
+]
+key5 = "";
+key6 = "";
+key7 = " \\n";
+key8 = 1048576;
+key9 = 3.140000;
+key10 = true;
+key11 = false;
+
diff --git a/src/ucl/tests/rcl_test.json.xz b/src/ucl/tests/rcl_test.json.xz
new file mode 100644
index 000000000..98c3559ad
--- /dev/null
+++ b/src/ucl/tests/rcl_test.json.xz
Binary files differ
diff --git a/src/ucl/tests/run_tests.sh b/src/ucl/tests/run_tests.sh
new file mode 100755
index 000000000..71e6f1b08
--- /dev/null
+++ b/src/ucl/tests/run_tests.sh
@@ -0,0 +1,57 @@
+#!/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
+
+if [ $# -gt 1 -a -x "/usr/bin/xz" ] ; 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/src/ucl/tests/test_basic.c b/src/ucl/tests/test_basic.c
new file mode 100644
index 000000000..d4d4786d6
--- /dev/null
+++ b/src/ucl/tests/test_basic.c
@@ -0,0 +1,113 @@
+/* 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"
+
+int
+main (int argc, char **argv)
+{
+ char inbuf[8192];
+ struct ucl_parser *parser, *parser2;
+ ucl_object_t *obj;
+ FILE *in, *out;
+ UT_string *err = NULL;
+ unsigned char *emitted;
+ const char *fname_in = NULL, *fname_out = NULL;
+ int ret = 0;
+
+ switch (argc) {
+ case 2:
+ fname_in = argv[1];
+ break;
+ case 3:
+ fname_in = argv[1];
+ fname_out = argv[2];
+ break;
+ }
+
+ if (fname_in != NULL) {
+ in = fopen (fname_in, "r");
+ if (in == NULL) {
+ exit (-errno);
+ }
+ }
+ else {
+ in = stdin;
+ }
+ parser = ucl_parser_new (UCL_FLAG_KEY_LOWERCASE);
+
+ while (!feof (in)) {
+ fread (inbuf, sizeof (inbuf), 1, in);
+ ucl_parser_add_chunk (parser, inbuf, strlen (inbuf), &err);
+ }
+ fclose (in);
+
+ if (fname_out != NULL) {
+ out = fopen (fname_out, "w");
+ if (out == NULL) {
+ exit (-errno);
+ }
+ }
+ else {
+ out = stdout;
+ }
+ if (err != NULL) {
+ fprintf (out, "Error occurred: %s\n", err->d);
+ ret = 1;
+ goto end;
+ }
+ obj = ucl_parser_get_object (parser, &err);
+ emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG);
+ ucl_parser_free (parser);
+ ucl_object_unref (obj);
+ parser2 = ucl_parser_new (UCL_FLAG_KEY_LOWERCASE);
+ ucl_parser_add_chunk (parser2, emitted, strlen (emitted), &err);
+
+ if (err != NULL) {
+ fprintf (out, "Error occurred: %s\n", err->d);
+ fprintf (out, "%s\n", emitted);
+ ret = 1;
+ goto end;
+ }
+ if (emitted != NULL) {
+ free (emitted);
+ }
+ obj = ucl_parser_get_object (parser2, &err);
+ 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);
+ }
+ fclose (out);
+
+ return ret;
+}
diff --git a/src/ucl/tests/test_generate.c b/src/ucl/tests/test_generate.c
new file mode 100644
index 000000000..a61b35396
--- /dev/null
+++ b/src/ucl/tests/test_generate.c
@@ -0,0 +1,98 @@
+/* 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"
+
+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_new ();
+ /* 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);
+ 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);
+
+ 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/src/ucl/tests/test_speed.c b/src/ucl/tests/test_speed.c
new file mode 100644
index 000000000..92a972aa5
--- /dev/null
+++ b/src/ucl/tests/test_speed.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.
+ */
+
+#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>
+
+#include "ucl.h"
+
+int
+main (int argc, char **argv)
+{
+ void *map;
+ struct ucl_parser *parser;
+ ucl_object_t *obj;
+ int fin;
+ UT_string *err = NULL;
+ unsigned char *emitted;
+ struct stat st;
+ const char *fname_in = NULL;
+ int ret = 0;
+ struct timespec start, end;
+ double 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_FLAG_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);
+
+ clock_gettime (CLOCK_MONOTONIC, &start);
+ ucl_parser_add_chunk (parser, map, st.st_size, &err);
+
+ obj = ucl_parser_get_object (parser, &err);
+ clock_gettime (CLOCK_MONOTONIC, &end);
+
+ seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.;
+ printf ("ucl: parsed input in %.4f seconds\n", seconds);
+ if (err != NULL) {
+ printf ("Error occurred: %s\n", err->d);
+ ret = 1;
+ }
+
+ clock_gettime (CLOCK_MONOTONIC, &start);
+ emitted = ucl_object_emit (obj, UCL_EMIT_CONFIG);
+ clock_gettime (CLOCK_MONOTONIC, &end);
+
+ seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.;
+ printf ("ucl: emitted config in %.4f seconds\n", seconds);
+
+ free (emitted);
+
+ clock_gettime (CLOCK_MONOTONIC, &start);
+ emitted = ucl_object_emit (obj, UCL_EMIT_JSON);
+ clock_gettime (CLOCK_MONOTONIC, &end);
+
+ seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.;
+ printf ("ucl: emitted json in %.4f seconds\n", seconds);
+
+ free (emitted);
+
+ clock_gettime (CLOCK_MONOTONIC, &start);
+ emitted = ucl_object_emit (obj, UCL_EMIT_JSON_COMPACT);
+ clock_gettime (CLOCK_MONOTONIC, &end);
+
+ seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.;
+ printf ("ucl: emitted compact json in %.4f seconds\n", seconds);
+
+ free (emitted);
+
+ clock_gettime (CLOCK_MONOTONIC, &start);
+ emitted = ucl_object_emit (obj, UCL_EMIT_YAML);
+ clock_gettime (CLOCK_MONOTONIC, &end);
+
+ seconds = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1000000000.;
+ printf ("ucl: emitted yaml in %.4f seconds\n", seconds);
+
+ free (emitted);
+
+ ucl_parser_free (parser);
+ ucl_object_unref (obj);
+
+ munmap (map, st.st_size);
+
+ return ret;
+}
diff --git a/src/ucl/utils/chargen.c b/src/ucl/utils/chargen.c
new file mode 100644
index 000000000..724b37899
--- /dev/null
+++ b/src/ucl/utils/chargen.c
@@ -0,0 +1,124 @@
+/* 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 (!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/src/ucl/utils/objdump.c b/src/ucl/utils/objdump.c
new file mode 100644
index 000000000..985106a09
--- /dev/null
+++ b/src/ucl/utils/objdump.c
@@ -0,0 +1,158 @@
+/* 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;
+
+ pre[--num] = 0x00;
+ while (num--)
+ pre[num] = 0x20;
+
+ while (obj != NULL ) {
+ printf ("%sucl object address: %p\n", pre + 4, obj);
+ if (obj->hh.key != NULL) {
+ printf ("%skey: \"%s\"\n", pre, ucl_object_key (obj));
+ }
+ printf ("%sref: %d\n", pre, obj->ref);
+ printf ("%slen: %zd\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);
+ HASH_ITER (hh, obj->value.ov, cur, tmp) {
+ 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.ov);
+ ucl_obj_dump (obj->value.ov, shift + 2);
+ }
+ else if (obj->type == UCL_INT) {
+ printf ("%stype: UCL_INT\n", pre);
+ printf ("%svalue: %ld\n", pre, obj->value.iv);
+ }
+ else if (obj->type == UCL_FLOAT) {
+ printf ("%stype: UCL_FLOAT\n", pre);
+ printf ("%svalue: %f\n", pre, obj->value.dv);
+ }
+ else if (obj->type == UCL_STRING) {
+ printf ("%stype: UCL_STRING\n", pre);
+ printf ("%svalue: \"%s\"\n", pre, ucl_obj_tostring (obj));
+ }
+ else if (obj->type == UCL_BOOLEAN) {
+ printf ("%stype: UCL_BOOLEAN\n", pre);
+ printf ("%svalue: %s\n", pre, (obj->value.iv) ? "true" : "false");
+ }
+ else if (obj->type == UCL_TIME) {
+ printf ("%stype: UCL_TIME\n", pre);
+ printf ("%svalue: %f\n", pre, obj->value.dv);
+ }
+ else if (obj->type == UCL_USERDATA) {
+ printf ("%stype: UCL_USERDATA\n", pre);
+ printf ("%svalue: %p\n", pre, obj->value.ud);
+ }
+ obj = obj->next;
+ }
+
+ free (pre);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *fn = NULL;
+ char inbuf[8192];
+ struct ucl_parser *parser;
+ UT_string *err = NULL;
+ int k, ret = 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)) {
+ fread (inbuf, sizeof (inbuf), 1, in);
+ ucl_parser_add_chunk (parser, inbuf, strlen (inbuf), &err);
+ }
+ fclose (in);
+ if (err != NULL ) {
+ printf ("Error occured: %s\n", err->d);
+ ret = 1;
+ goto end;
+ }
+
+ obj = ucl_parser_get_object (parser, &err);
+ if (err != NULL ) {
+ printf ("Error occured: %s\n", err->d);
+ ret = 1;
+ goto end;
+ }
+
+ if (argc > 2) {
+ for (k = 2; k < argc; k++) {
+ printf ("search for \"%s\"... ", argv[k]);
+ par = ucl_obj_get_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_obj_unref (obj);
+ }
+
+ return ret;
+}