aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYING23
-rw-r--r--ChangeLog.md16
-rw-r--r--Makefile.am6
-rw-r--r--Makefile.w327
-rw-r--r--README.md58
-rw-r--r--cmake/CMakeLists.txt18
-rw-r--r--configure.ac29
-rw-r--r--doc/lua_api.md194
-rw-r--r--include/lua_ucl.h69
-rw-r--r--include/ucl.h181
-rw-r--r--libucl.pc.in2
-rw-r--r--lua/Makefile.am26
-rw-r--r--lua/libucl.rockspec.in26
-rw-r--r--lua/lua_ucl.c820
-rw-r--r--lua/test.lua48
-rw-r--r--m4/.gitignore4
-rw-r--r--m4/ax_lua.m4606
-rw-r--r--src/ucl_emitter.c64
-rw-r--r--src/ucl_emitter_streamline.c3
-rw-r--r--src/ucl_emitter_utils.c58
-rw-r--r--src/ucl_hash.c15
-rw-r--r--src/ucl_hash.h6
-rw-r--r--src/ucl_internal.h40
-rw-r--r--src/ucl_parser.c422
-rw-r--r--src/ucl_util.c581
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/basic/12.in2
-rw-r--r--tests/basic/12.res3
-rw-r--r--tests/basic/13.in9
-rw-r--r--tests/basic/13.res8
-rw-r--r--tests/basic/4.res22
-rw-r--r--tests/basic/comments.in25
-rw-r--r--tests/basic/comments.res7
-rw-r--r--tests/basic/include_dir/invalid.conf1
-rw-r--r--tests/basic/include_dir/pri1.conf2
-rw-r--r--tests/basic/include_dir/pri2.conf2
-rw-r--r--tests/basic/include_dir/test1.conf1
-rw-r--r--tests/basic/include_dir/test2.conf1
-rw-r--r--tests/basic/include_dir/test3.conf1
-rw-r--r--tests/generate.res1
-rw-r--r--tests/test_basic.c4
-rw-r--r--tests/test_generate.c7
-rw-r--r--tests/test_schema.c2
-rw-r--r--utils/objdump.c1
44 files changed, 3160 insertions, 263 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 000000000000..33048e1b8b97
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,23 @@
+Copyright (c) 2013-2014, Vsevolod Stakhov <vsevolod@highsecure.ru>
+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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/ChangeLog.md b/ChangeLog.md
index 09c331f4c6d1..0df5e072222e 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -4,3 +4,19 @@
- Streamline emitter has been added, so it is now possible to output partial `ucl` objects
- Emitter now is more flexible due to emitter_context structure
+
+### 0.5.1
+- Fixed number of bugs and memory leaks
+
+### 0.5.2
+
+- Allow userdata objects to be emitted and destructed
+- Use userdata objects to store lua function references
+
+### Libucl 0.6
+
+- Reworked macro interface
+
+### Libucl 0.6.1
+
+- Various utilities fixes
diff --git a/Makefile.am b/Makefile.am
index 4668b3681849..ece5b97df488 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4,4 +4,8 @@ EXTRA_DIST = uthash README.md
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libucl.pc
-SUBDIRS = src tests utils doc \ No newline at end of file
+if LUA_SUB
+ LUA_SUBDIR = lua
+endif
+
+SUBDIRS = src tests utils doc $(LUA_SUBDIR) \ No newline at end of file
diff --git a/Makefile.w32 b/Makefile.w32
index 62ff14a3a296..0e61274ff59c 100644
--- a/Makefile.w32
+++ b/Makefile.w32
@@ -33,6 +33,7 @@ OBJECTS = $(OBJDIR)/ucl_hash.o \
$(OBJDIR)/ucl_util.o \
$(OBJDIR)/ucl_parser.o \
$(OBJDIR)/ucl_emitter.o \
+ $(OBJDIR)/ucl_emitter_utils.o \
$(OBJDIR)/ucl_schema.o \
$(OBJDIR)/xxhash.o
@@ -51,6 +52,8 @@ $(OBJDIR)/ucl_parser.o: $(SRCDIR)/ucl_parser.c $(HDEPS)
$(CC) -o $(OBJDIR)/ucl_parser.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_parser.c
$(OBJDIR)/ucl_emitter.o: $(SRCDIR)/ucl_emitter.c $(HDEPS)
$(CC) -o $(OBJDIR)/ucl_emitter.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_emitter.c
+$(OBJDIR)/ucl_emitter_utils.o: $(SRCDIR)/ucl_emitter_utils.c $(HDEPS)
+ $(CC) -o $(OBJDIR)/ucl_emitter_utils.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_emitter_utils.c
$(OBJDIR)/ucl_hash.o: $(SRCDIR)/ucl_hash.c $(HDEPS)
$(CC) -o $(OBJDIR)/ucl_hash.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_hash.c
$(OBJDIR)/ucl_schema.o: $(SRCDIR)/ucl_schema.c $(HDEPS)
@@ -61,7 +64,7 @@ $(OBJDIR)/xxhash.o: $(SRCDIR)/xxhash.c $(HDEPS)
clean:
$(RM) $(OBJDIR)/*.o $(OBJDIR)/$(SONAME) $(OBJDIR)/$(SONAME) $(OBJDIR)/chargen $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(OBJDIR)/objdump $(OBJDIR)/test_generate
$(RMDIR) $(OBJDIR)
-
+
# Utils
chargen: utils/chargen.c $(OBJDIR)/$(SONAME)
@@ -75,7 +78,7 @@ test: $(OBJDIR) $(OBJDIR)/$(SONAME) $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(
run-test: test
TEST_DIR=$(TESTDIR) $(TESTDIR)/run_tests.sh $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(OBJDIR)/test_generate
-
+
$(OBJDIR)/test_basic: $(TESTDIR)/test_basic.c $(OBJDIR)/$(SONAME)
$(CC) -o $(OBJDIR)/test_basic $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) $(LDFLAGS) $(TESTDIR)/test_basic.c $(LD_UCL_FLAGS)
$(OBJDIR)/test_speed: $(TESTDIR)/test_speed.c $(OBJDIR)/$(SONAME)
diff --git a/README.md b/README.md
index b6353d10ae27..235cfb6595df 100644
--- a/README.md
+++ b/README.md
@@ -223,15 +223,57 @@ UCL supports external macros both multiline and single line ones:
....
};
```
-There are two internal macros provided by UCL:
-* `include` - read a file `/path/to/file` or an url `http://example.com/file` and include it to the current place of
-UCL configuration;
-* `try\_include` - try to read a file or url and include it but do not create a fatal error if a file or url is not accessible;
-* `includes` - read a file or an url like the previous macro, but fetch and check the signature file (which is obtained
-by `.sig` suffix appending).
+Moreover, each macro can accept an optional list of arguments in braces. These
+arguments themselves are the UCL object that is parsed and passed to a macro as
+options:
-Public keys which are used for the last command are specified by the concrete UCL user.
+```nginx
+.macro(param=value) "something";
+.macro(param={key=value}) "something";
+.macro(.include "params.conf") "something";
+.macro(#this is multiline macro
+param = [value1, value2]) "something";
+.macro(key="()") "something";
+```
+
+UCL also provide a convenient `include` macro to load content from another files
+to the current UCL object. This macro accepts either path to file:
+
+```nginx
+.include "/full/path.conf"
+.include "./relative/path.conf"
+.include "${CURDIR}/path.conf"
+```
+
+or URL (if ucl is built with url support provided by either `libcurl` or `libfetch`):
+
+ .include "http://example.com/file.conf"
+
+`.include` macro supports a set of options:
+
+* `try` (default: **false**) - if this option is `true` than UCL treats errors on loading of
+this file as non-fatal. For example, such a file can be absent but it won't stop the parsing
+of the top-level document.
+* `sign` (default: **false**) - if this option is `true` UCL loads and checks the signature for
+a file from path named `<FILEPATH>.sig`. Trusted public keys should be provided for UCL API after
+parser is created but before any configurations are parsed.
+* `glob` (default: **false**) - if this option is `true` UCL treats the filename as GLOB pattern and load
+all files that matches the specified pattern (normally the format of patterns is defined in `glob` manual page
+for your operating system). This option is meaningless for URL includes.
+* `url` (default: **true**) - allow URL includes.
+* `priority` (default: 0) - specify priority for the include (see below).
+
+Priorities are used by UCL parser to manage the policy of objects rewriting during including other files
+as following:
+
+* If we have two objects with the same priority then we form an implicit array
+* If a new object has bigger priority then we overwrite an old one
+* If a new object has lower priority then we ignore it
+
+By default, the priority of top-level object is set to zero (lowest priority). Currently,
+you can define up to 16 priorities (from 0 to 15). Includes with bigger priorities will
+rewrite keys from the objects with lower priorities as specified by the policy.
### Variables support
@@ -317,7 +359,7 @@ 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.
+You can do your own benchmarks by running `make check` in libucl top directory.
## Conclusion
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index 4551966135ea..3c57db53784a 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -1,8 +1,8 @@
PROJECT(libucl C)
SET(LIBUCL_VERSION_MAJOR 0)
-SET(LIBUCL_VERSION_MINOR 2)
-SET(LIBUCL_VERSION_PATCH 9)
+SET(LIBUCL_VERSION_MINOR 5)
+SET(LIBUCL_VERSION_PATCH 0)
SET(LIBUCL_VERSION "${LIBUCL_VERSION_MAJOR}.${LIBUCL_VERSION_MINOR}.${LIBUCL_VERSION_PATCH}")
@@ -86,6 +86,8 @@ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/../uthash")
SET(UCLSRC ../src/ucl_util.c
../src/ucl_parser.c
../src/ucl_emitter.c
+ ../src/ucl_emitter_streamline.c
+ ../src/ucl_emitter_utils.c
../src/ucl_hash.c
../src/ucl_schema.c
../src/xxhash.c)
@@ -98,6 +100,18 @@ ENDIF (BUILD_SHARED_LIBS)
ADD_LIBRARY(ucl ${LIB_TYPE} ${UCLSRC})
SET_TARGET_PROPERTIES(ucl PROPERTIES VERSION ${LIBUCL_VERSION} SOVERSION ${LIBUCL_VERSION_MAJOR})
+IF(WITH_LUA)
+ SET(UCL_LUA_SRC ../lua/lua_ucl.c)
+ ADD_LIBRARY(lua-ucl ${LIB_TYPE} ${UCL_LUA_SRC})
+ IF(ENABLE_LUAJIT MATCHES "ON")
+ TARGET_LINK_LIBRARIES(lua-ucl "${LUAJIT_LIBRARY}")
+ ELSE(ENABLE_LUAJIT MATCHES "ON")
+ TARGET_LINK_LIBRARIES(lua-ucl "${LUA_LIBRARY}")
+ ENDIF(ENABLE_LUAJIT MATCHES "ON")
+ TARGET_LINK_LIBRARIES(lua-ucl ucl)
+ SET_TARGET_PROPERTIES(lua-ucl PROPERTIES VERSION ${LIBUCL_VERSION} SOVERSION ${LIBUCL_VERSION_MAJOR})
+ENDIF(WITH_LUA)
+
IF(HAVE_FETCH_H)
TARGET_LINK_LIBRARIES(ucl fetch)
ELSE(HAVE_FETCH_H)
diff --git a/configure.ac b/configure.ac
index 2d612f6236e8..32db73ad0fdd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,12 +1,13 @@
m4_define([maj_ver], [0])
-m4_define([med_ver], [5])
-m4_define([min_ver], [0])
-m4_define([so_version], [2:0:0])
+m4_define([med_ver], [6])
+m4_define([min_ver], [1])
+m4_define([so_version], [3:0:1])
m4_define([ucl_version], [maj_ver.med_ver.min_ver])
AC_INIT([libucl],[ucl_version],[https://github.com/vstakhov/libucl],[libucl])
AC_CONFIG_SRCDIR([configure.ac])
-AM_INIT_AUTOMAKE([1.11 foreign silent-rules -Wall -Wportability no-dist-gzip dist-xz])
+AM_INIT_AUTOMAKE([1.11 foreign -Wall -Wportability no-dist-gzip dist-xz])
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
UCL_VERSION=ucl_version
SO_VERSION=so_version
@@ -57,6 +58,9 @@ AC_ARG_ENABLE([regex], AS_HELP_STRING([--enable-regex],
AC_ARG_ENABLE([signatures], AS_HELP_STRING([--enable-signatures],
[Enable signatures check (requires openssl) @<:@default=no@:>@]), [],
[enable_signatures=no])
+AC_ARG_ENABLE([lua], AS_HELP_STRING([--enable-lua],
+ [Enable lua API build (requires lua libraries and headers) @<:@default=no@:>@]), [],
+ [enable_lua=no])
AC_ARG_ENABLE([utils],
AS_HELP_STRING([--enable-utils], [Build and install utils @<:@default=no@:>@]),
[case "${enableval}" in
@@ -99,6 +103,21 @@ AS_IF([test "x$enable_regex" = "xyes"], [
])
AC_SUBST(LIBREGEX_LIB)
+AS_IF([test "x$enable_lua" = "xyes"], [
+ AX_PROG_LUA([5.1], [], [
+ AX_LUA_HEADERS([
+ AX_LUA_LIBS([
+ AC_DEFINE(HAVE_LUA, 1, [Define to 1 for lua support.])
+ with_lua="yes"
+ ], [AC_MSG_ERROR([unable to find the lua libraries])
+ ])
+ ], [AC_MSG_ERROR([unable to find the lua header files])
+ ])
+ ], [AC_MSG_ERROR([unable to find the lua interpreter])])
+], [with_lua="no"])
+
+AM_CONDITIONAL([LUA_SUB], [test "$with_lua" = "yes"])
+
AS_IF([test "x$enable_urls" = "xyes"], [
AC_CHECK_HEADER([fetch.h], [
AC_DEFINE(HAVE_FETCH_H, 1, [Define to 1 if you have the <fetch.h> header file.])
@@ -155,9 +174,11 @@ AC_LINK_IFELSE([
AC_CONFIG_FILES(Makefile \
src/Makefile \
+ lua/Makefile
tests/Makefile \
utils/Makefile \
doc/Makefile \
+ lua/libucl.rockspec \
libucl.pc)
AC_CONFIG_FILES([stamp-h], [echo timestamp > stamp-h])
AC_OUTPUT
diff --git a/doc/lua_api.md b/doc/lua_api.md
new file mode 100644
index 000000000000..a53353b3c57e
--- /dev/null
+++ b/doc/lua_api.md
@@ -0,0 +1,194 @@
+## Module `ucl`
+
+This lua module allows to parse objects from strings and to store data into
+ucl objects. It uses `libucl` C library to parse and manipulate with ucl objects.
+
+Example:
+
+~~~lua
+local ucl = require("ucl")
+
+local parser = ucl.parser()
+local res,err = parser:parse_string('{key=value}')
+
+if not res then
+ print('parser error: ' .. err)
+else
+ local obj = parser:get_object()
+ local got = ucl.to_format(obj, 'json')
+endif
+
+local table = {
+ str = 'value',
+ num = 100500,
+ null = ucl.null,
+ func = function ()
+ return 'huh'
+ end
+
+
+print(ucl.to_format(table, 'ucl'))
+-- Output:
+--[[
+num = 100500;
+str = "value";
+null = null;
+func = "huh";
+--]]
+~~~
+
+###Brief content:
+
+**Functions**:
+
+> [`ucl_object_push_lua(L, obj, allow_array)`](#function-ucl_object_push_lual-obj-allow_array)
+
+> [`ucl.to_format(var, format)`](#function-uclto_formatvar-format)
+
+
+
+**Methods**:
+
+> [`parser:parse_file(name)`](#method-parserparse_filename)
+
+> [`parser:parse_string(input)`](#method-parserparse_stringinput)
+
+> [`parser:get_object()`](#method-parserget_object)
+
+
+## Functions
+
+The module `ucl` defines the following functions.
+
+### Function `ucl_object_push_lua(L, obj, allow_array)`
+
+This is a `C` function to push `UCL` object as lua variable. This function
+converts `obj` to lua representation using the following conversions:
+
+- *scalar* values are directly presented by lua objects
+- *userdata* values are converted to lua function objects using `LUA_REGISTRYINDEX`,
+this can be used to pass functions from lua to c and vice-versa
+- *arrays* are converted to lua tables with numeric indicies suitable for `ipairs` iterations
+- *objects* are converted to lua tables with string indicies
+
+**Parameters:**
+
+- `L {lua_State}`: lua state pointer
+- `obj {ucl_object_t}`: object to push
+- `allow_array {bool}`: expand implicit arrays (should be true for all but partial arrays)
+
+**Returns:**
+
+- `{int}`: `1` if an object is pushed to lua
+
+Back to [module description](#module-ucl).
+
+### Function `ucl.to_format(var, format)`
+
+Converts lua variable `var` to the specified `format`. Formats supported are:
+
+- `json` - fine printed json
+- `json-compact` - compacted json
+- `config` - fine printed configuration
+- `ucl` - same as `config`
+- `yaml` - embedded yaml
+
+If `var` contains function, they are called during output formatting and if
+they return string value, then this value is used for ouptut.
+
+**Parameters:**
+
+- `var {variant}`: any sort of lua variable (if userdata then metafield `__to_ucl` is searched for output)
+- `format {string}`: any available format
+
+**Returns:**
+
+- `{string}`: string representation of `var` in the specific `format`.
+
+Example:
+
+~~~lua
+local table = {
+ str = 'value',
+ num = 100500,
+ null = ucl.null,
+ func = function ()
+ return 'huh'
+ end
+
+
+print(ucl.to_format(table, 'ucl'))
+-- Output:
+--[[
+num = 100500;
+str = "value";
+null = null;
+func = "huh";
+--]]
+~~~
+
+Back to [module description](#module-ucl).
+
+
+## Methods
+
+The module `ucl` defines the following methods.
+
+### Method `parser:parse_file(name)`
+
+Parse UCL object from file.
+
+**Parameters:**
+
+- `name {string}`: filename to parse
+
+**Returns:**
+
+- `{bool[, string]}`: if res is `true` then file has been parsed successfully, otherwise an error string is also returned
+
+Example:
+
+~~~lua
+local parser = ucl.parser()
+local res,err = parser:parse_file('/some/file.conf')
+
+if not res then
+ print('parser error: ' .. err)
+else
+ -- Do something with object
+end
+~~~
+
+Back to [module description](#module-ucl).
+
+### Method `parser:parse_string(input)`
+
+Parse UCL object from file.
+
+**Parameters:**
+
+- `input {string}`: string to parse
+
+**Returns:**
+
+- `{bool[, string]}`: if res is `true` then file has been parsed successfully, otherwise an error string is also returned
+
+Back to [module description](#module-ucl).
+
+### Method `parser:get_object()`
+
+Get top object from parser and export it to lua representation.
+
+**Parameters:**
+
+ nothing
+
+**Returns:**
+
+- `{variant or nil}`: ucl object as lua native variable
+
+Back to [module description](#module-ucl).
+
+
+Back to [top](#).
+
diff --git a/include/lua_ucl.h b/include/lua_ucl.h
new file mode 100644
index 000000000000..38e74d3f619d
--- /dev/null
+++ b/include/lua_ucl.h
@@ -0,0 +1,69 @@
+/* Copyright (c) 2014, 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 LUA_UCL_H_
+#define LUA_UCL_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+#include "ucl.h"
+
+/**
+ * Closure structure for lua function storing inside UCL
+ */
+struct ucl_lua_funcdata {
+ lua_State *L;
+ int idx;
+ char *ret;
+};
+
+/**
+ * Initialize lua UCL API
+ */
+UCL_EXTERN int luaopen_ucl (lua_State *L);
+
+/**
+ * Import UCL object from lua state
+ * @param L lua state
+ * @param idx index of object at the lua stack to convert to UCL
+ * @return new UCL object or NULL, the caller should unref object after using
+ */
+UCL_EXTERN ucl_object_t* ucl_object_lua_import (lua_State *L, int idx);
+
+/**
+ * Push an object to lua
+ * @param L lua state
+ * @param obj object to push
+ * @param allow_array traverse over implicit arrays
+ */
+UCL_EXTERN int ucl_object_push_lua (lua_State *L,
+ const ucl_object_t *obj, bool allow_array);
+
+UCL_EXTERN struct ucl_lua_funcdata* ucl_object_toclosure (
+ const ucl_object_t *obj);
+
+#endif /* LUA_UCL_H_ */
diff --git a/include/ucl.h b/include/ucl.h
index b40f11322508..1aac6a25332c 100644
--- a/include/ucl.h
+++ b/include/ucl.h
@@ -147,7 +147,8 @@ typedef enum ucl_emitter {
typedef enum ucl_parser_flags {
UCL_PARSER_KEY_LOWERCASE = 0x1, /**< Convert all keys to lower case */
UCL_PARSER_ZEROCOPY = 0x2, /**< Parse input in zero-copy mode if possible */
- UCL_PARSER_NO_TIME = 0x4 /**< Do not parse time and treat time values as strings */
+ UCL_PARSER_NO_TIME = 0x4, /**< Do not parse time and treat time values as strings */
+ UCL_PARSER_NO_IMPLICIT_ARRAYS = 0x8 /** Create explicit arrays instead of implicit ones */
} ucl_parser_flags_t;
/**
@@ -171,9 +172,12 @@ typedef enum ucl_string_flags {
* Basic flags for an object
*/
typedef enum ucl_object_flags {
- UCL_OBJECT_ALLOCATED_KEY = 1, /**< An object has key allocated internally */
- UCL_OBJECT_ALLOCATED_VALUE = 2, /**< An object has a string value allocated internally */
- UCL_OBJECT_NEED_KEY_ESCAPE = 4 /**< The key of an object need to be escaped on output */
+ UCL_OBJECT_ALLOCATED_KEY = 0x1, /**< An object has key allocated internally */
+ UCL_OBJECT_ALLOCATED_VALUE = 0x2, /**< An object has a string value allocated internally */
+ UCL_OBJECT_NEED_KEY_ESCAPE = 0x4, /**< The key of an object need to be escaped on output */
+ UCL_OBJECT_EPHEMERAL = 0x8, /**< Temporary object that does not need to be freed really */
+ UCL_OBJECT_MULTILINE = 0x10, /**< String should be displayed as multiline string */
+ UCL_OBJECT_MULTIVALUE = 0x20 /**< Object is a key with multiple values */
} ucl_object_flags_t;
/**
@@ -195,14 +199,21 @@ typedef struct ucl_object_s {
const char *key; /**< Key of an object */
struct ucl_object_s *next; /**< Array handle */
struct ucl_object_s *prev; /**< Array handle */
- unsigned char* trash_stack[2]; /**< Pointer to allocated chunks */
- unsigned keylen; /**< Lenght of a key */
- unsigned len; /**< Size of an object */
- enum ucl_type type; /**< Real type */
- uint16_t ref; /**< Reference count */
+ uint32_t keylen; /**< Lenght of a key */
+ uint32_t len; /**< Size of an object */
+ uint32_t ref; /**< Reference count */
uint16_t flags; /**< Object flags */
+ uint16_t type; /**< Real type */
+ unsigned char* trash_stack[2]; /**< Pointer to allocated chunks */
} ucl_object_t;
+/**
+ * Destructor type for userdata objects
+ * @param ud user specified data pointer
+ */
+typedef void (*ucl_userdata_dtor)(void *ud);
+typedef const char* (*ucl_userdata_emitter)(void *ud);
+
/** @} */
/**
@@ -239,6 +250,31 @@ UCL_EXTERN ucl_object_t* ucl_object_new (void) UCL_WARN_UNUSED_RESULT;
UCL_EXTERN ucl_object_t* ucl_object_typed_new (ucl_type_t type) UCL_WARN_UNUSED_RESULT;
/**
+ * Create new object with type and priority specified
+ * @param type type of a new object
+ * @param priority priority of an object
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_new_full (ucl_type_t type, unsigned priority)
+ UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Create new object with userdata dtor
+ * @param dtor destructor function
+ * @return new object
+ */
+UCL_EXTERN ucl_object_t* ucl_object_new_userdata (ucl_userdata_dtor dtor,
+ ucl_userdata_emitter emitter) UCL_WARN_UNUSED_RESULT;
+
+/**
+ * Perform deep copy of an object copying everything
+ * @param other object to copy
+ * @return new object with refcount equal to 1
+ */
+UCL_EXTERN ucl_object_t * ucl_object_copy (const ucl_object_t *other)
+ UCL_WARN_UNUSED_RESULT;
+
+/**
* Return the type of an object
* @return the object type
*/
@@ -293,7 +329,7 @@ UCL_EXTERN ucl_object_t* ucl_object_frombool (bool bv) UCL_WARN_UNUSED_RESULT;
/**
* 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 top destination object (must be of type UCL_OBJECT)
* @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)
@@ -306,7 +342,7 @@ UCL_EXTERN bool ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt,
/**
* Replace a object 'elt' to the hash 'top' and associate it with key 'key', old object will be unrefed,
* if no object has been found this function works like ucl_object_insert_key()
- * @param top destination object (will be created automatically if top is NULL)
+ * @param top destination object (must be of type UCL_OBJECT)
* @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)
@@ -317,6 +353,15 @@ UCL_EXTERN bool ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt,
const char *key, size_t keylen, bool copy_key);
/**
+ * Merge the keys from one object to another object. Overwrite on conflict
+ * @param top destination object (must be of type UCL_OBJECT)
+ * @param elt element to insert (must be of type UCL_OBJECT)
+ * @param copy copy rather than reference the elements
+ * @return true if all keys have been merged
+ */
+UCL_EXTERN bool ucl_object_merge (ucl_object_t *top, ucl_object_t *elt, bool copy);
+
+/**
* Delete a object associated with key 'key', old object will be unrefered,
* @param top object
* @param key key associated to the object to remove
@@ -335,8 +380,9 @@ UCL_EXTERN bool ucl_object_delete_key (ucl_object_t *top,
/**
- * Delete key from `top` object returning the object deleted. This object is not
- * released
+ * Removes `key` from `top` object, returning the object that was removed. This
+ * object is not released, caller must unref the returned object when it is no
+ * longer needed.
* @param top object
* @param key key to remove
* @param keylen length of the key (or 0 for NULL terminated keys)
@@ -346,8 +392,9 @@ UCL_EXTERN ucl_object_t* ucl_object_pop_keyl (ucl_object_t *top, const char *key
size_t keylen) UCL_WARN_UNUSED_RESULT;
/**
- * Delete key from `top` object returning the object deleted. This object is not
- * released
+ * Removes `key` from `top` object returning the object that was removed. This
+ * object is not released, caller must unref the returned object when it is no
+ * longer needed.
* @param top object
* @param key key to remove
* @return removed object or NULL if object has not been found
@@ -356,9 +403,9 @@ UCL_EXTERN ucl_object_t* ucl_object_pop_key (ucl_object_t *top, const char *key)
UCL_WARN_UNUSED_RESULT;
/**
- * Insert a object 'elt' to the hash 'top' and associate it with key 'key', if the specified key exist,
- * try to merge its content
- * @param top destination object (will be created automatically if top is NULL)
+ * Insert a object 'elt' to the hash 'top' and associate it with key 'key', if
+ * the specified key exist, try to merge its content
+ * @param top destination object (must be of type UCL_OBJECT)
* @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)
@@ -369,8 +416,8 @@ UCL_EXTERN bool ucl_object_insert_key_merged (ucl_object_t *top, ucl_object_t *e
const char *key, size_t keylen, bool copy_key);
/**
- * Append an element to the front of array object
- * @param top destination object (will be created automatically if top is NULL)
+ * Append an element to the end of array object
+ * @param top destination object (must NOT be NULL)
* @param elt element to append (must NOT be NULL)
* @return true if value has been inserted
*/
@@ -379,7 +426,7 @@ UCL_EXTERN bool ucl_array_append (ucl_object_t *top,
/**
* Append an element to the start of array object
- * @param top destination object (will be created automatically if top is NULL)
+ * @param top destination object (must NOT be NULL)
* @param elt element to append (must NOT be NULL)
* @return true if value has been inserted
*/
@@ -387,8 +434,19 @@ UCL_EXTERN bool ucl_array_prepend (ucl_object_t *top,
ucl_object_t *elt);
/**
- * Removes an element `elt` from the array `top`. Caller must unref the returned object when it is not
- * needed.
+ * Merge all elements of second array into the first array
+ * @param top destination array (must be of type UCL_ARRAY)
+ * @param elt array to copy elements from (must be of type UCL_ARRAY)
+ * @param copy copy elements instead of referencing them
+ * @return true if arrays were merged
+ */
+UCL_EXTERN bool ucl_array_merge (ucl_object_t *top, ucl_object_t *elt,
+ bool copy);
+
+/**
+ * Removes an element `elt` from the array `top`, returning the object that was
+ * removed. This object is not released, caller must unref the returned object
+ * when it is no longer needed.
* @param top array ucl object
* @param elt element to remove
* @return removed element or NULL if `top` is NULL or not an array
@@ -411,35 +469,50 @@ UCL_EXTERN const ucl_object_t* ucl_array_head (const ucl_object_t *top);
UCL_EXTERN const ucl_object_t* ucl_array_tail (const ucl_object_t *top);
/**
- * Removes the last element from the array `top`. Caller must unref the returned object when it is not
- * needed.
+ * Removes the last element from the array `top`, returning the object that was
+ * removed. This object is not released, caller must unref the returned object
+ * when it is no longer needed.
* @param top array ucl object
* @return removed element or NULL if `top` is NULL or not an array
*/
UCL_EXTERN ucl_object_t* ucl_array_pop_last (ucl_object_t *top);
/**
- * Return object identified by an index of the array `top`
- * @param obj object to get a key from (must be of type UCL_ARRAY)
- * @param index index to return
+ * Removes the first element from the array `top`, returning the object that was
+ * removed. This object is not released, caller must unref the returned object
+ * when it is no longer needed.
+ * @param top array ucl object
+ * @return removed element or NULL if `top` is NULL or not an array
+ */
+UCL_EXTERN ucl_object_t* ucl_array_pop_first (ucl_object_t *top);
+
+/**
+ * Return object identified by index of the array `top`
+ * @param top object to get a key from (must be of type UCL_ARRAY)
+ * @param index array index to return
* @return object at the specified index or NULL if index is not found
*/
UCL_EXTERN const ucl_object_t* ucl_array_find_index (const ucl_object_t *top,
unsigned int index);
/**
- * Removes the first element from the array `top`. Caller must unref the returned object when it is not
- * needed.
- * @param top array ucl object
- * @return removed element or NULL if `top` is NULL or not an array
+ * Replace an element in an array with a different element, returning the object
+ * that was replaced. This object is not released, caller must unref the
+ * returned object when it is no longer needed.
+ * @param top destination object (must be of type UCL_ARRAY)
+ * @param elt element to append (must NOT be NULL)
+ * @param index array index in destination to overwrite with elt
+ * @return object that was replaced or NULL if index is not found
*/
-UCL_EXTERN ucl_object_t* ucl_array_pop_first (ucl_object_t *top);
+ucl_object_t *
+ucl_array_replace_index (ucl_object_t *top, ucl_object_t *elt,
+ unsigned int index);
/**
* Append a element to another element forming an implicit array
* @param head head to append (may be NULL)
* @param elt new element
- * @return true if element has been inserted
+ * @return the new implicit array
*/
UCL_EXTERN ucl_object_t * ucl_elt_append (ucl_object_t *head,
ucl_object_t *elt);
@@ -533,7 +606,7 @@ UCL_EXTERN const char* ucl_object_tolstring (const ucl_object_t *obj, size_t *tl
* 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
+ * @return object matching the specified key or NULL if key was not found
*/
UCL_EXTERN const ucl_object_t* ucl_object_find_key (const ucl_object_t *obj,
const char *key);
@@ -543,7 +616,7 @@ UCL_EXTERN const ucl_object_t* ucl_object_find_key (const ucl_object_t *obj,
* @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
+ * @return object matching the specified key or NULL if key was not found
*/
UCL_EXTERN const ucl_object_t* ucl_object_find_keyl (const ucl_object_t *obj,
const char *key, size_t klen);
@@ -575,6 +648,7 @@ UCL_EXTERN const char* ucl_object_keyl (const ucl_object_t *obj, size_t *len);
/**
* Increase reference count for an object
* @param obj object to ref
+ * @return the referenced object
*/
UCL_EXTERN ucl_object_t* ucl_object_ref (const ucl_object_t *obj);
@@ -612,6 +686,21 @@ UCL_EXTERN void ucl_object_array_sort (ucl_object_t *ar,
int (*cmp)(const ucl_object_t *o1, const ucl_object_t *o2));
/**
+ * Get the priority for specific UCL object
+ * @param obj any ucl object
+ * @return priority of an object
+ */
+UCL_EXTERN unsigned int ucl_object_get_priority (const ucl_object_t *obj);
+
+/**
+ * Set explicit priority of an object.
+ * @param obj any ucl object
+ * @param priority new priroity value (only 4 least significant bits are considred)
+ */
+UCL_EXTERN void ucl_object_set_priority (ucl_object_t *obj,
+ unsigned int priority);
+
+/**
* Opaque iterator object
*/
typedef void* ucl_object_iter_t;
@@ -640,11 +729,14 @@ UCL_EXTERN const ucl_object_t* ucl_iterate_object (const ucl_object_t *obj,
* Macro handler for a parser
* @param data the content of macro
* @param len the length of content
+ * @param arguments arguments object
* @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);
+typedef bool (*ucl_macro_handler) (const unsigned char *data, size_t len,
+ const ucl_object_t *arguments,
+ void* ud);
/* Opaque parser */
struct ucl_parser;
@@ -702,13 +794,24 @@ UCL_EXTERN void ucl_parser_set_variables_handler (struct ucl_parser *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
*/
UCL_EXTERN bool ucl_parser_add_chunk (struct ucl_parser *parser,
const unsigned char *data, size_t len);
/**
+ * Load new chunk to a parser with the specified priority
+ * @param parser parser structure
+ * @param data the pointer to the beginning of a chunk
+ * @param len the length of a chunk
+ * @param priority the desired priority of a chunk (only 4 least significant bits
+ * are considered for this parameter)
+ * @return true if chunk has been added and false in case of error
+ */
+UCL_EXTERN bool ucl_parser_add_chunk_priority (struct ucl_parser *parser,
+ const unsigned char *data, size_t len, unsigned priority);
+
+/**
* Load ucl object from a string
* @param parser parser structure
* @param data the pointer to the string
@@ -835,7 +938,7 @@ struct ucl_emitter_context {
/** A set of output operations */
const struct ucl_emitter_operations *ops;
/** Current amount of indent tabs */
- unsigned int ident;
+ unsigned int indent;
/** Top level object */
const ucl_object_t *top;
/** The rest of context */
diff --git a/libucl.pc.in b/libucl.pc.in
index 975462d21fdc..3433fa9d8b1c 100644
--- a/libucl.pc.in
+++ b/libucl.pc.in
@@ -7,5 +7,5 @@ Name: LibUCL
Description: Universal configuration library
Version: @UCL_VERSION@
Libs: -L${libdir} -lucl
-Libs.private: @LIBS_EXTRA@
+Libs.private: @LIBS_EXTRA@ @LUA_LIB@
Cflags: -I${includedir}/
diff --git a/lua/Makefile.am b/lua/Makefile.am
new file mode 100644
index 000000000000..95beafbfc94e
--- /dev/null
+++ b/lua/Makefile.am
@@ -0,0 +1,26 @@
+ucl_common_cflags= -I$(top_srcdir)/src \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/uthash \
+ -Wall -W -Wno-unused-parameter -Wno-pointer-sign
+luaexec_LTLIBRARIES= ucl.la
+ucl_la_SOURCES= lua_ucl.c
+ucl_la_CFLAGS= $(ucl_common_cflags) \
+ @LUA_INCLUDE@
+ucl_la_LDFLAGS = -module -export-dynamic -avoid-version
+ucl_la_LIBADD= $(top_srcdir)/src/libucl.la \
+ @LIBFETCH_LIBS@ \
+ @LIBCRYPTO_LIB@ \
+ @LIBREGEX_LIB@ \
+ @CURL_LIBS@ \
+ @LUA_LIB@
+
+include_HEADERS= $(top_srcdir)/include/lua_ucl.h
+
+ROCKSPEC = $(PACKAGE)-$(VERSION)-1.rockspec
+EXTRA_DIST = $(PACKAGE).rockspec.in \
+ test.lua
+DISTCLEANFILES = $(PACKAGE).rockspec
+
+$(ROCKSPEC): $(PACKAGE).rockspec dist
+ sed -e 's/@MD5@/'`$(MD5SUM) $(distdir).tar.gz | \
+ cut -d " " -f 1`'/g' < $(PACKAGE).rockspec > $@ \ No newline at end of file
diff --git a/lua/libucl.rockspec.in b/lua/libucl.rockspec.in
new file mode 100644
index 000000000000..52f39176a7bd
--- /dev/null
+++ b/lua/libucl.rockspec.in
@@ -0,0 +1,26 @@
+package="@PACKAGE@"
+version="@VERSION@-1"
+source = {
+ url = "https://github.com/downloads/vstakhov/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz",
+ md5 = "@MD5@",
+ dir = "@PACKAGE@-@VERSION@"
+}
+description = {
+ summary = "UCL - json like configuration language",
+ detailed = [[
+ 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.
+ ]],
+ homepage = "http://github.com/vstakhov/@PACKAGE@/",
+ license = ""
+}
+dependencies = {
+ "lua >= 5.1"
+}
+build = {
+ type = "command",
+ build_command = "LUA=$(LUA) CPPFLAGS=-I$(LUA_INCDIR) ./configure --prefix=$(PREFIX) --libdir=$(LIBDIR) --datadir=$(LUADIR) && make clean && make",
+ install_command = "make install"
+}
diff --git a/lua/lua_ucl.c b/lua/lua_ucl.c
new file mode 100644
index 000000000000..682b0b559fd3
--- /dev/null
+++ b/lua/lua_ucl.c
@@ -0,0 +1,820 @@
+/* Copyright (c) 2014, 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 lua ucl bindings
+ */
+
+#include "ucl.h"
+#include "ucl_internal.h"
+#include "lua_ucl.h"
+#include <strings.h>
+
+/***
+ * @module ucl
+ * This lua module allows to parse objects from strings and to store data into
+ * ucl objects. It uses `libucl` C library to parse and manipulate with ucl objects.
+ * @example
+local ucl = require("ucl")
+
+local parser = ucl.parser()
+local res,err = parser:parse_string('{key=value}')
+
+if not res then
+ print('parser error: ' .. err)
+else
+ local obj = parser:get_object()
+ local got = ucl.to_format(obj, 'json')
+endif
+
+local table = {
+ str = 'value',
+ num = 100500,
+ null = ucl.null,
+ func = function ()
+ return 'huh'
+ end
+}
+
+print(ucl.to_format(table, 'ucl'))
+-- Output:
+--[[
+num = 100500;
+str = "value";
+null = null;
+func = "huh";
+--]]
+ */
+
+#define PARSER_META "ucl.parser.meta"
+#define EMITTER_META "ucl.emitter.meta"
+#define NULL_META "null.emitter.meta"
+
+static int ucl_object_lua_push_array (lua_State *L, const ucl_object_t *obj);
+static int ucl_object_lua_push_scalar (lua_State *L, const ucl_object_t *obj, bool allow_array);
+static ucl_object_t* ucl_object_lua_fromtable (lua_State *L, int idx);
+static ucl_object_t* ucl_object_lua_fromelt (lua_State *L, int idx);
+
+static void *ucl_null;
+
+/**
+ * Push a single element of an object to lua
+ * @param L
+ * @param key
+ * @param obj
+ */
+static void
+ucl_object_lua_push_element (lua_State *L, const char *key,
+ const ucl_object_t *obj)
+{
+ lua_pushstring (L, key);
+ ucl_object_push_lua (L, obj, true);
+ lua_settable (L, -3);
+}
+
+static void
+lua_ucl_userdata_dtor (void *ud)
+{
+ struct ucl_lua_funcdata *fd = (struct ucl_lua_funcdata *)ud;
+
+ luaL_unref (fd->L, LUA_REGISTRYINDEX, fd->idx);
+ if (fd->ret != NULL) {
+ free (fd->ret);
+ }
+ free (fd);
+}
+
+static const char *
+lua_ucl_userdata_emitter (void *ud)
+{
+ struct ucl_lua_funcdata *fd = (struct ucl_lua_funcdata *)ud;
+ const char *out = "";
+
+ lua_rawgeti (fd->L, LUA_REGISTRYINDEX, fd->idx);
+
+ lua_pcall (fd->L, 0, 1, 0);
+ out = lua_tostring (fd->L, -1);
+
+ if (out != NULL) {
+ /* We need to store temporary string in a more appropriate place */
+ if (fd->ret) {
+ free (fd->ret);
+ }
+ fd->ret = strdup (out);
+ }
+
+ lua_settop (fd->L, 0);
+
+ return fd->ret;
+}
+
+/**
+ * Push a single object to lua
+ * @param L
+ * @param obj
+ * @return
+ */
+static int
+ucl_object_lua_push_object (lua_State *L, const ucl_object_t *obj,
+ bool allow_array)
+{
+ const ucl_object_t *cur;
+ ucl_object_iter_t it = NULL;
+ int nelt = 0;
+
+ if (allow_array && obj->next != NULL) {
+ /* Actually we need to push this as an array */
+ return ucl_object_lua_push_array (L, obj);
+ }
+
+ /* Optimize allocation by preallocation of table */
+ while (ucl_iterate_object (obj, &it, true) != NULL) {
+ nelt ++;
+ }
+
+ lua_createtable (L, 0, nelt);
+ it = NULL;
+
+ while ((cur = ucl_iterate_object (obj, &it, true)) != NULL) {
+ ucl_object_lua_push_element (L, ucl_object_key (cur), cur);
+ }
+
+ return 1;
+}
+
+/**
+ * Push an array to lua as table indexed by integers
+ * @param L
+ * @param obj
+ * @return
+ */
+static int
+ucl_object_lua_push_array (lua_State *L, const ucl_object_t *obj)
+{
+ const ucl_object_t *cur;
+ int i = 1, nelt = 0;
+
+ /* Optimize allocation by preallocation of table */
+ LL_FOREACH (obj, cur) {
+ nelt ++;
+ }
+
+ lua_createtable (L, nelt, 0);
+
+ LL_FOREACH (obj, cur) {
+ ucl_object_push_lua (L, cur, false);
+ lua_rawseti (L, -2, i);
+ i ++;
+ }
+
+ return 1;
+}
+
+/**
+ * Push a simple object to lua depending on its actual type
+ */
+static int
+ucl_object_lua_push_scalar (lua_State *L, const ucl_object_t *obj,
+ bool allow_array)
+{
+ struct ucl_lua_funcdata *fd;
+
+ if (allow_array && obj->next != NULL) {
+ /* Actually we need to push this as an array */
+ return ucl_object_lua_push_array (L, obj);
+ }
+
+ switch (obj->type) {
+ case UCL_BOOLEAN:
+ lua_pushboolean (L, ucl_obj_toboolean (obj));
+ break;
+ case UCL_STRING:
+ lua_pushstring (L, ucl_obj_tostring (obj));
+ break;
+ case UCL_INT:
+#if LUA_VERSION_NUM >= 501
+ lua_pushinteger (L, ucl_obj_toint (obj));
+#else
+ lua_pushnumber (L, ucl_obj_toint (obj));
+#endif
+ break;
+ case UCL_FLOAT:
+ case UCL_TIME:
+ lua_pushnumber (L, ucl_obj_todouble (obj));
+ break;
+ case UCL_NULL:
+ lua_getfield (L, LUA_REGISTRYINDEX, "ucl.null");
+ break;
+ case UCL_USERDATA:
+ fd = (struct ucl_lua_funcdata *)obj->value.ud;
+ lua_rawgeti (L, LUA_REGISTRYINDEX, fd->idx);
+ break;
+ default:
+ lua_pushnil (L);
+ break;
+ }
+
+ return 1;
+}
+
+/***
+ * @function ucl_object_push_lua(L, obj, allow_array)
+ * This is a `C` function to push `UCL` object as lua variable. This function
+ * converts `obj` to lua representation using the following conversions:
+ *
+ * - *scalar* values are directly presented by lua objects
+ * - *userdata* values are converted to lua function objects using `LUA_REGISTRYINDEX`,
+ * this can be used to pass functions from lua to c and vice-versa
+ * - *arrays* are converted to lua tables with numeric indicies suitable for `ipairs` iterations
+ * - *objects* are converted to lua tables with string indicies
+ * @param {lua_State} L lua state pointer
+ * @param {ucl_object_t} obj object to push
+ * @param {bool} allow_array expand implicit arrays (should be true for all but partial arrays)
+ * @return {int} `1` if an object is pushed to lua
+ */
+int
+ucl_object_push_lua (lua_State *L, const ucl_object_t *obj, bool allow_array)
+{
+ switch (obj->type) {
+ case UCL_OBJECT:
+ return ucl_object_lua_push_object (L, obj, allow_array);
+ case UCL_ARRAY:
+ return ucl_object_lua_push_array (L, obj->value.av);
+ default:
+ return ucl_object_lua_push_scalar (L, obj, allow_array);
+ }
+}
+
+/**
+ * Parse lua table into object top
+ * @param L
+ * @param top
+ * @param idx
+ */
+static ucl_object_t *
+ucl_object_lua_fromtable (lua_State *L, int idx)
+{
+ ucl_object_t *obj, *top = NULL;
+ size_t keylen;
+ const char *k;
+ bool is_array = true;
+ int max = INT_MIN;
+
+ if (idx < 0) {
+ /* For negative indicies we want to invert them */
+ idx = lua_gettop (L) + idx + 1;
+ }
+ /* Check for array */
+ lua_pushnil (L);
+ while (lua_next (L, idx) != 0) {
+ if (lua_type (L, -2) == LUA_TNUMBER) {
+ double num = lua_tonumber (L, -2);
+ if (num == (int)num) {
+ if (num > max) {
+ max = num;
+ }
+ }
+ else {
+ /* Keys are not integer */
+ lua_pop (L, 2);
+ is_array = false;
+ break;
+ }
+ }
+ else {
+ /* Keys are not numeric */
+ lua_pop (L, 2);
+ is_array = false;
+ break;
+ }
+ lua_pop (L, 1);
+ }
+
+ /* Table iterate */
+ if (is_array) {
+ int i;
+
+ top = ucl_object_typed_new (UCL_ARRAY);
+ for (i = 1; i <= max; i ++) {
+ lua_pushinteger (L, i);
+ lua_gettable (L, idx);
+ obj = ucl_object_lua_fromelt (L, lua_gettop (L));
+ if (obj != NULL) {
+ ucl_array_append (top, obj);
+ }
+ }
+ }
+ else {
+ lua_pushnil (L);
+ top = ucl_object_typed_new (UCL_OBJECT);
+ while (lua_next (L, idx) != 0) {
+ /* copy key to avoid modifications */
+ k = lua_tolstring (L, -2, &keylen);
+ obj = ucl_object_lua_fromelt (L, lua_gettop (L));
+
+ if (obj != NULL) {
+ ucl_object_insert_key (top, obj, k, keylen, true);
+ }
+ lua_pop (L, 1);
+ }
+ }
+
+ return top;
+}
+
+/**
+ * Get a single element from lua to object obj
+ * @param L
+ * @param obj
+ * @param idx
+ */
+static ucl_object_t *
+ucl_object_lua_fromelt (lua_State *L, int idx)
+{
+ int type;
+ double num;
+ ucl_object_t *obj = NULL;
+ struct ucl_lua_funcdata *fd;
+
+ type = lua_type (L, idx);
+
+ switch (type) {
+ case LUA_TSTRING:
+ obj = ucl_object_fromstring_common (lua_tostring (L, idx), 0, 0);
+ break;
+ case LUA_TNUMBER:
+ num = lua_tonumber (L, idx);
+ if (num == (int64_t)num) {
+ obj = ucl_object_fromint (num);
+ }
+ else {
+ obj = ucl_object_fromdouble (num);
+ }
+ break;
+ case LUA_TBOOLEAN:
+ obj = ucl_object_frombool (lua_toboolean (L, idx));
+ break;
+ case LUA_TUSERDATA:
+ if (lua_topointer (L, idx) == ucl_null) {
+ obj = ucl_object_typed_new (UCL_NULL);
+ }
+ break;
+ case LUA_TTABLE:
+ case LUA_TFUNCTION:
+ case LUA_TTHREAD:
+ if (luaL_getmetafield (L, idx, "__gen_ucl")) {
+ if (lua_isfunction (L, -1)) {
+ lua_settop (L, 3); /* gen, obj, func */
+ lua_insert (L, 1); /* func, gen, obj */
+ lua_insert (L, 2); /* func, obj, gen */
+ lua_call(L, 2, 1);
+ obj = ucl_object_lua_fromelt (L, 1);
+ }
+ lua_pop (L, 2);
+ }
+ else {
+ if (type == LUA_TTABLE) {
+ obj = ucl_object_lua_fromtable (L, idx);
+ }
+ else if (type == LUA_TFUNCTION) {
+ fd = malloc (sizeof (*fd));
+ if (fd != NULL) {
+ lua_pushvalue (L, idx);
+ fd->L = L;
+ fd->ret = NULL;
+ fd->idx = luaL_ref (L, LUA_REGISTRYINDEX);
+
+ obj = ucl_object_new_userdata (lua_ucl_userdata_dtor,
+ lua_ucl_userdata_emitter);
+ obj->type = UCL_USERDATA;
+ obj->value.ud = (void *)fd;
+ }
+ }
+ }
+ break;
+ }
+
+ return obj;
+}
+
+/**
+ * @function ucl_object_lua_import(L, idx)
+ * Extracts ucl object from lua variable at `idx` position,
+ * @see ucl_object_push_lua for conversion definitions
+ * @param {lua_state} L lua state machine pointer
+ * @param {int} idx index where the source variable is placed
+ * @return {ucl_object_t} new ucl object extracted from lua variable. Reference count of this object is 1,
+ * this object thus needs to be unref'ed after usage.
+ */
+ucl_object_t *
+ucl_object_lua_import (lua_State *L, int idx)
+{
+ ucl_object_t *obj;
+ int t;
+
+ t = lua_type (L, idx);
+ switch (t) {
+ case LUA_TTABLE:
+ obj = ucl_object_lua_fromtable (L, idx);
+ break;
+ default:
+ obj = ucl_object_lua_fromelt (L, idx);
+ break;
+ }
+
+ return obj;
+}
+
+static int
+lua_ucl_parser_init (lua_State *L)
+{
+ struct ucl_parser *parser, **pparser;
+ int flags = 0;
+
+ if (lua_gettop (L) >= 1) {
+ flags = lua_tonumber (L, 1);
+ }
+
+ parser = ucl_parser_new (flags);
+ if (parser == NULL) {
+ lua_pushnil (L);
+ }
+
+ pparser = lua_newuserdata (L, sizeof (parser));
+ *pparser = parser;
+ luaL_getmetatable (L, PARSER_META);
+ lua_setmetatable (L, -2);
+
+ return 1;
+}
+
+static struct ucl_parser *
+lua_ucl_parser_get (lua_State *L, int index)
+{
+ return *((struct ucl_parser **) luaL_checkudata(L, index, PARSER_META));
+}
+
+/***
+ * @method parser:parse_file(name)
+ * Parse UCL object from file.
+ * @param {string} name filename to parse
+ * @return {bool[, string]} if res is `true` then file has been parsed successfully, otherwise an error string is also returned
+@example
+local parser = ucl.parser()
+local res,err = parser:parse_file('/some/file.conf')
+
+if not res then
+ print('parser error: ' .. err)
+else
+ -- Do something with object
+end
+ */
+static int
+lua_ucl_parser_parse_file (lua_State *L)
+{
+ struct ucl_parser *parser;
+ const char *file;
+ int ret = 2;
+
+ parser = lua_ucl_parser_get (L, 1);
+ file = luaL_checkstring (L, 2);
+
+ if (parser != NULL && file != NULL) {
+ if (ucl_parser_add_file (parser, file)) {
+ lua_pushboolean (L, true);
+ ret = 1;
+ }
+ else {
+ lua_pushboolean (L, false);
+ lua_pushstring (L, ucl_parser_get_error (parser));
+ }
+ }
+ else {
+ lua_pushboolean (L, false);
+ lua_pushstring (L, "invalid arguments");
+ }
+
+ return ret;
+}
+
+/***
+ * @method parser:parse_string(input)
+ * Parse UCL object from file.
+ * @param {string} input string to parse
+ * @return {bool[, string]} if res is `true` then file has been parsed successfully, otherwise an error string is also returned
+ */
+static int
+lua_ucl_parser_parse_string (lua_State *L)
+{
+ struct ucl_parser *parser;
+ const char *string;
+ size_t llen;
+ int ret = 2;
+
+ parser = lua_ucl_parser_get (L, 1);
+ string = luaL_checklstring (L, 2, &llen);
+
+ if (parser != NULL && string != NULL) {
+ if (ucl_parser_add_chunk (parser, (const unsigned char *)string, llen)) {
+ lua_pushboolean (L, true);
+ ret = 1;
+ }
+ else {
+ lua_pushboolean (L, false);
+ lua_pushstring (L, ucl_parser_get_error (parser));
+ }
+ }
+ else {
+ lua_pushboolean (L, false);
+ lua_pushstring (L, "invalid arguments");
+ }
+
+ return ret;
+}
+
+/***
+ * @method parser:get_object()
+ * Get top object from parser and export it to lua representation.
+ * @return {variant or nil} ucl object as lua native variable
+ */
+static int
+lua_ucl_parser_get_object (lua_State *L)
+{
+ struct ucl_parser *parser;
+ ucl_object_t *obj;
+ int ret = 1;
+
+ parser = lua_ucl_parser_get (L, 1);
+ obj = ucl_parser_get_object (parser);
+
+ if (obj != NULL) {
+ ret = ucl_object_push_lua (L, obj, false);
+ /* no need to keep reference */
+ ucl_object_unref (obj);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return ret;
+}
+
+static int
+lua_ucl_parser_gc (lua_State *L)
+{
+ struct ucl_parser *parser;
+
+ parser = lua_ucl_parser_get (L, 1);
+ ucl_parser_free (parser);
+
+ return 0;
+}
+
+static void
+lua_ucl_parser_mt (lua_State *L)
+{
+ luaL_newmetatable (L, PARSER_META);
+
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -2, "__index");
+
+ lua_pushcfunction (L, lua_ucl_parser_gc);
+ lua_setfield (L, -2, "__gc");
+
+ lua_pushcfunction (L, lua_ucl_parser_parse_file);
+ lua_setfield (L, -2, "parse_file");
+
+ lua_pushcfunction (L, lua_ucl_parser_parse_string);
+ lua_setfield (L, -2, "parse_string");
+
+ lua_pushcfunction (L, lua_ucl_parser_get_object);
+ lua_setfield (L, -2, "get_object");
+
+ lua_pop (L, 1);
+}
+
+static int
+lua_ucl_to_string (lua_State *L, const ucl_object_t *obj, enum ucl_emitter type)
+{
+ unsigned char *result;
+
+ result = ucl_object_emit (obj, type);
+
+ if (result != NULL) {
+ lua_pushstring (L, (const char *)result);
+ free (result);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
+static int
+lua_ucl_to_json (lua_State *L)
+{
+ ucl_object_t *obj;
+ int format = UCL_EMIT_JSON;
+
+ if (lua_gettop (L) > 1) {
+ if (lua_toboolean (L, 2)) {
+ format = UCL_EMIT_JSON_COMPACT;
+ }
+ }
+
+ obj = ucl_object_lua_import (L, 1);
+ if (obj != NULL) {
+ lua_ucl_to_string (L, obj, format);
+ ucl_object_unref (obj);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
+static int
+lua_ucl_to_config (lua_State *L)
+{
+ ucl_object_t *obj;
+
+ obj = ucl_object_lua_import (L, 1);
+ if (obj != NULL) {
+ lua_ucl_to_string (L, obj, UCL_EMIT_CONFIG);
+ ucl_object_unref (obj);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
+/***
+ * @function ucl.to_format(var, format)
+ * Converts lua variable `var` to the specified `format`. Formats supported are:
+ *
+ * - `json` - fine printed json
+ * - `json-compact` - compacted json
+ * - `config` - fine printed configuration
+ * - `ucl` - same as `config`
+ * - `yaml` - embedded yaml
+ *
+ * If `var` contains function, they are called during output formatting and if
+ * they return string value, then this value is used for ouptut.
+ * @param {variant} var any sort of lua variable (if userdata then metafield `__to_ucl` is searched for output)
+ * @param {string} format any available format
+ * @return {string} string representation of `var` in the specific `format`.
+ * @example
+local table = {
+ str = 'value',
+ num = 100500,
+ null = ucl.null,
+ func = function ()
+ return 'huh'
+ end
+}
+
+print(ucl.to_format(table, 'ucl'))
+-- Output:
+--[[
+num = 100500;
+str = "value";
+null = null;
+func = "huh";
+--]]
+ */
+static int
+lua_ucl_to_format (lua_State *L)
+{
+ ucl_object_t *obj;
+ int format = UCL_EMIT_JSON;
+
+ if (lua_gettop (L) > 1) {
+ if (lua_type (L, 2) == LUA_TNUMBER) {
+ format = lua_tonumber (L, 2);
+ if (format < 0 || format >= UCL_EMIT_YAML) {
+ lua_pushnil (L);
+ return 1;
+ }
+ }
+ else if (lua_type (L, 2) == LUA_TSTRING) {
+ const char *strtype = lua_tostring (L, 2);
+
+ if (strcasecmp (strtype, "json") == 0) {
+ format = UCL_EMIT_JSON;
+ }
+ else if (strcasecmp (strtype, "json-compact") == 0) {
+ format = UCL_EMIT_JSON_COMPACT;
+ }
+ else if (strcasecmp (strtype, "yaml") == 0) {
+ format = UCL_EMIT_YAML;
+ }
+ else if (strcasecmp (strtype, "config") == 0 ||
+ strcasecmp (strtype, "ucl") == 0) {
+ format = UCL_EMIT_CONFIG;
+ }
+ }
+ }
+
+ obj = ucl_object_lua_import (L, 1);
+ if (obj != NULL) {
+ lua_ucl_to_string (L, obj, format);
+ ucl_object_unref (obj);
+ }
+ else {
+ lua_pushnil (L);
+ }
+
+ return 1;
+}
+
+static int
+lua_ucl_null_tostring (lua_State* L)
+{
+ lua_pushstring (L, "null");
+ return 1;
+}
+
+static void
+lua_ucl_null_mt (lua_State *L)
+{
+ luaL_newmetatable (L, NULL_META);
+
+ lua_pushcfunction (L, lua_ucl_null_tostring);
+ lua_setfield (L, -2, "__tostring");
+
+ lua_pop (L, 1);
+}
+
+int
+luaopen_ucl (lua_State *L)
+{
+ lua_ucl_parser_mt (L);
+ lua_ucl_null_mt (L);
+
+ /* Create the refs weak table: */
+ lua_createtable (L, 0, 2);
+ lua_pushliteral (L, "v"); /* tbl, "v" */
+ lua_setfield (L, -2, "__mode");
+ lua_pushvalue (L, -1); /* tbl, tbl */
+ lua_setmetatable (L, -2); /* tbl */
+ lua_setfield (L, LUA_REGISTRYINDEX, "ucl.refs");
+
+ lua_newtable (L);
+
+ lua_pushcfunction (L, lua_ucl_parser_init);
+ lua_setfield (L, -2, "parser");
+
+ lua_pushcfunction (L, lua_ucl_to_json);
+ lua_setfield (L, -2, "to_json");
+
+ lua_pushcfunction (L, lua_ucl_to_config);
+ lua_setfield (L, -2, "to_config");
+
+ lua_pushcfunction (L, lua_ucl_to_format);
+ lua_setfield (L, -2, "to_format");
+
+ ucl_null = lua_newuserdata (L, 0);
+ luaL_getmetatable (L, NULL_META);
+ lua_setmetatable (L, -2);
+
+ lua_pushvalue (L, -1);
+ lua_setfield (L, LUA_REGISTRYINDEX, "ucl.null");
+
+ lua_setfield (L, -2, "null");
+
+ return 1;
+}
+
+struct ucl_lua_funcdata*
+ucl_object_toclosure (const ucl_object_t *obj)
+{
+ if (obj == NULL || obj->type != UCL_USERDATA) {
+ return NULL;
+ }
+
+ return (struct ucl_lua_funcdata*)obj->value.ud;
+}
diff --git a/lua/test.lua b/lua/test.lua
new file mode 100644
index 000000000000..1e7b3b3e0bdb
--- /dev/null
+++ b/lua/test.lua
@@ -0,0 +1,48 @@
+local ucl = require("ucl")
+
+function test_simple()
+ local expect =
+ '['..
+ '"float",1.5,'..
+ '"integer",5,'..
+ '"true",true,'..
+ '"false",false,'..
+ '"null",null,'..
+ '"string","hello",'..
+ '"array",[1,2],'..
+ '"object",{"key":"value"}'..
+ ']'
+
+ -- Input to to_value matches the output of to_string:
+ local parser = ucl.parser()
+ local res,err = parser:parse_string(expect)
+ if not res then
+ print('parser error: ' .. err)
+ return 1
+ end
+
+ local obj = parser:get_object()
+ local got = ucl.to_json(obj, true)
+ if expect == got then
+ return 0
+ else
+ print(expect .. " == " .. tostring(got))
+ return 1
+ end
+end
+
+test_simple()
+
+local table = {
+ str = 'value',
+ num = 100500,
+ null = ucl.null,
+ func = function ()
+ return 'huh'
+ end,
+ badfunc = function()
+ print("I'm bad")
+ end
+}
+
+print(ucl.to_format(table, 'ucl'))
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 000000000000..5e7d2734cfc6
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
diff --git a/m4/ax_lua.m4 b/m4/ax_lua.m4
new file mode 100644
index 000000000000..7662f4f22d1b
--- /dev/null
+++ b/m4/ax_lua.m4
@@ -0,0 +1,606 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_lua.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PROG_LUA[([MINIMUM-VERSION], [TOO-BIG-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])]
+# AX_LUA_HEADERS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])]
+# AX_LUA_LIBS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])]
+# AX_LUA_READLINE[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])]
+#
+# DESCRIPTION
+#
+# Detect a Lua interpreter, optionally specifying a minimum and maximum
+# version number. Set up important Lua paths, such as the directories in
+# which to install scripts and modules (shared libraries).
+#
+# Also detect Lua headers and libraries. The Lua version contained in the
+# header is checked to match the Lua interpreter version exactly. When
+# searching for Lua libraries, the version number is used as a suffix.
+# This is done with the goal of supporting multiple Lua installs (5.1 and
+# 5.2 side-by-side).
+#
+# A note on compatibility with previous versions: This file has been
+# mostly rewritten for serial 18. Most developers should be able to use
+# these macros without needing to modify configure.ac. Care has been taken
+# to preserve each macro's behavior, but there are some differences:
+#
+# 1) AX_WITH_LUA is deprecated; it now expands to the exact same thing as
+# AX_PROG_LUA with no arguments.
+#
+# 2) AX_LUA_HEADERS now checks that the version number defined in lua.h
+# matches the interpreter version. AX_LUA_HEADERS_VERSION is therefore
+# unnecessary, so it is deprecated and does not expand to anything.
+#
+# 3) The configure flag --with-lua-suffix no longer exists; the user
+# should instead specify the LUA precious variable on the command line.
+# See the AX_PROG_LUA description for details.
+#
+# Please read the macro descriptions below for more information.
+#
+# This file was inspired by Andrew Dalke's and James Henstridge's
+# python.m4 and Tom Payne's, Matthieu Moy's, and Reuben Thomas's ax_lua.m4
+# (serial 17). Basically, this file is a mash-up of those two files. I
+# like to think it combines the best of the two!
+#
+# AX_PROG_LUA: Search for the Lua interpreter, and set up important Lua
+# paths. Adds precious variable LUA, which may contain the path of the Lua
+# interpreter. If LUA is blank, the user's path is searched for an
+# suitable interpreter.
+#
+# If MINIMUM-VERSION is supplied, then only Lua interpreters with a
+# version number greater or equal to MINIMUM-VERSION will be accepted. If
+# TOO-BIG- VERSION is also supplied, then only Lua interpreters with a
+# version number greater or equal to MINIMUM-VERSION and less than
+# TOO-BIG-VERSION will be accepted.
+#
+# The Lua version number, LUA_VERSION, is found from the interpreter, and
+# substituted. LUA_PLATFORM is also found, but not currently supported (no
+# standard representation).
+#
+# Finally, the macro finds four paths:
+#
+# luadir Directory to install Lua scripts.
+# pkgluadir $luadir/$PACKAGE
+# luaexecdir Directory to install Lua modules.
+# pkgluaexecdir $luaexecdir/$PACKAGE
+#
+# These paths a found based on $prefix, $exec_prefix, Lua's package.path,
+# and package.cpath. The first path of package.path beginning with $prefix
+# is selected as luadir. The first path of package.cpath beginning with
+# $exec_prefix is used as luaexecdir. This should work on all reasonable
+# Lua installations. If a path cannot be determined, a default path is
+# used. Of course, the user can override these later when invoking make.
+#
+# luadir Default: $prefix/share/lua/$LUA_VERSION
+# luaexecdir Default: $exec_prefix/lib/lua/$LUA_VERSION
+#
+# These directories can be used by Automake as install destinations. The
+# variable name minus 'dir' needs to be used as a prefix to the
+# appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES.
+#
+# If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is
+# performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT-
+# FOUND is blank, then it will default to printing an error. To prevent
+# the default behavior, give ':' as an action.
+#
+# AX_LUA_HEADERS: Search for Lua headers. Requires that AX_PROG_LUA be
+# expanded before this macro. Adds precious variable LUA_INCLUDE, which
+# may contain Lua specific include flags, e.g. -I/usr/include/lua5.1. If
+# LUA_INCLUDE is blank, then this macro will attempt to find suitable
+# flags.
+#
+# LUA_INCLUDE can be used by Automake to compile Lua modules or
+# executables with embedded interpreters. The *_CPPFLAGS variables should
+# be used for this purpose, e.g. myprog_CPPFLAGS = $(LUA_INCLUDE).
+#
+# This macro searches for the header lua.h (and others). The search is
+# performed with a combination of CPPFLAGS, CPATH, etc, and LUA_INCLUDE.
+# If the search is unsuccessful, then some common directories are tried.
+# If the headers are then found, then LUA_INCLUDE is set accordingly.
+#
+# The paths automatically searched are:
+#
+# * /usr/include/luaX.Y
+# * /usr/include/lua/X.Y
+# * /usr/include/luaXY
+# * /usr/local/include/luaX.Y
+# * /usr/local/include/lua-X.Y
+# * /usr/local/include/lua/X.Y
+# * /usr/local/include/luaXY
+#
+# (Where X.Y is the Lua version number, e.g. 5.1.)
+#
+# The Lua version number found in the headers is always checked to match
+# the Lua interpreter's version number. Lua headers with mismatched
+# version numbers are not accepted.
+#
+# If headers are found, then ACTION-IF-FOUND is performed, otherwise
+# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then
+# it will default to printing an error. To prevent the default behavior,
+# set the action to ':'.
+#
+# AX_LUA_LIBS: Search for Lua libraries. Requires that AX_PROG_LUA be
+# expanded before this macro. Adds precious variable LUA_LIB, which may
+# contain Lua specific linker flags, e.g. -llua5.1. If LUA_LIB is blank,
+# then this macro will attempt to find suitable flags.
+#
+# LUA_LIB can be used by Automake to link Lua modules or executables with
+# embedded interpreters. The *_LIBADD and *_LDADD variables should be used
+# for this purpose, e.g. mymod_LIBADD = $(LUA_LIB).
+#
+# This macro searches for the Lua library. More technically, it searches
+# for a library containing the function lua_load. The search is performed
+# with a combination of LIBS, LIBRARY_PATH, and LUA_LIB.
+#
+# If the search determines that some linker flags are missing, then those
+# flags will be added to LUA_LIB.
+#
+# If libraries are found, then ACTION-IF-FOUND is performed, otherwise
+# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then
+# it will default to printing an error. To prevent the default behavior,
+# set the action to ':'.
+#
+# AX_LUA_READLINE: Search for readline headers and libraries. Requires the
+# AX_LIB_READLINE macro, which is provided by ax_lib_readline.m4 from the
+# Autoconf Archive.
+#
+# If a readline compatible library is found, then ACTION-IF-FOUND is
+# performed, otherwise ACTION-IF-NOT-FOUND is performed.
+#
+# LICENSE
+#
+# Copyright (c) 2014 Reuben Thomas <rrt@sc3d.org>
+# Copyright (c) 2013 Tim Perkins <tprk77@gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 23
+
+dnl =========================================================================
+dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION],
+dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl =========================================================================
+AC_DEFUN([AX_PROG_LUA],
+[
+ dnl Make LUA a precious variable.
+ AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1])
+
+ dnl Find a Lua interpreter.
+ m4_define_default([_AX_LUA_INTERPRETER_LIST],
+ [lua lua5.2 lua52 lua5.1 lua51 lua50])
+
+ m4_if([$1], [],
+ [ dnl No version check is needed. Find any Lua interpreter.
+ AS_IF([test "x$LUA" = 'x'],
+ [AC_PATH_PROGS([LUA], [_AX_LUA_INTERPRETER_LIST], [:])])
+ ax_display_LUA='lua'
+
+ dnl At least check if this is a Lua interpreter.
+ AC_MSG_CHECKING([if $LUA is a Lua interpreter])
+ _AX_LUA_CHK_IS_INTRP([$LUA],
+ [AC_MSG_RESULT([yes])],
+ [ AC_MSG_RESULT([no])
+ AC_MSG_ERROR([not a Lua interpreter])
+ ])
+ ],
+ [ dnl A version check is needed.
+ AS_IF([test "x$LUA" != 'x'],
+ [ dnl Check if this is a Lua interpreter.
+ AC_MSG_CHECKING([if $LUA is a Lua interpreter])
+ _AX_LUA_CHK_IS_INTRP([$LUA],
+ [AC_MSG_RESULT([yes])],
+ [ AC_MSG_RESULT([no])
+ AC_MSG_ERROR([not a Lua interpreter])
+ ])
+ dnl Check the version.
+ m4_if([$2], [],
+ [_ax_check_text="whether $LUA version >= $1"],
+ [_ax_check_text="whether $LUA version >= $1, < $2"])
+ AC_MSG_CHECKING([$_ax_check_text])
+ _AX_LUA_CHK_VER([$LUA], [$1], [$2],
+ [AC_MSG_RESULT([yes])],
+ [ AC_MSG_RESULT([no])
+ AC_MSG_ERROR([version is out of range for specified LUA])])
+ ax_display_LUA=$LUA
+ ],
+ [ dnl Try each interpreter until we find one that satisfies VERSION.
+ m4_if([$2], [],
+ [_ax_check_text="for a Lua interpreter with version >= $1"],
+ [_ax_check_text="for a Lua interpreter with version >= $1, < $2"])
+ AC_CACHE_CHECK([$_ax_check_text],
+ [ax_cv_pathless_LUA],
+ [ for ax_cv_pathless_LUA in _AX_LUA_INTERPRETER_LIST none; do
+ test "x$ax_cv_pathless_LUA" = 'xnone' && break
+ _AX_LUA_CHK_IS_INTRP([$ax_cv_pathless_LUA], [], [continue])
+ _AX_LUA_CHK_VER([$ax_cv_pathless_LUA], [$1], [$2], [break])
+ done
+ ])
+ dnl Set $LUA to the absolute path of $ax_cv_pathless_LUA.
+ AS_IF([test "x$ax_cv_pathless_LUA" = 'xnone'],
+ [LUA=':'],
+ [AC_PATH_PROG([LUA], [$ax_cv_pathless_LUA])])
+ ax_display_LUA=$ax_cv_pathless_LUA
+ ])
+ ])
+
+ AS_IF([test "x$LUA" = 'x:'],
+ [ dnl Run any user-specified action, or abort.
+ m4_default([$4], [AC_MSG_ERROR([cannot find suitable Lua interpreter])])
+ ],
+ [ dnl Query Lua for its version number.
+ AC_CACHE_CHECK([for $ax_display_LUA version], [ax_cv_lua_version],
+ [ ax_cv_lua_version=`$LUA -e 'print(_VERSION:match "(%d+%.%d+)")'` ])
+ AS_IF([test "x$ax_cv_lua_version" = 'x'],
+ [AC_MSG_ERROR([invalid Lua version number])])
+ AC_SUBST([LUA_VERSION], [$ax_cv_lua_version])
+ AC_SUBST([LUA_SHORT_VERSION], [`echo "$LUA_VERSION" | sed 's|\.||'`])
+
+ dnl The following check is not supported:
+ dnl At times (like when building shared libraries) you may want to know
+ dnl which OS platform Lua thinks this is.
+ AC_CACHE_CHECK([for $ax_display_LUA platform], [ax_cv_lua_platform],
+ [ax_cv_lua_platform=`$LUA -e "print('unknown')"`])
+ AC_SUBST([LUA_PLATFORM], [$ax_cv_lua_platform])
+
+ dnl Use the values of $prefix and $exec_prefix for the corresponding
+ dnl values of LUA_PREFIX and LUA_EXEC_PREFIX. These are made distinct
+ dnl variables so they can be overridden if need be. However, the general
+ dnl consensus is that you shouldn't need this ability.
+ AC_SUBST([LUA_PREFIX], ['${prefix}'])
+ AC_SUBST([LUA_EXEC_PREFIX], ['${exec_prefix}'])
+
+ dnl Lua provides no way to query the script directory, and instead
+ dnl provides LUA_PATH. However, we should be able to make a safe educated
+ dnl guess. If the built-in search path contains a directory which is
+ dnl prefixed by $prefix, then we can store scripts there. The first
+ dnl matching path will be used.
+ AC_CACHE_CHECK([for $ax_display_LUA script directory],
+ [ax_cv_lua_luadir],
+ [ AS_IF([test "x$prefix" = 'xNONE'],
+ [ax_lua_prefix=$ac_default_prefix],
+ [ax_lua_prefix=$prefix])
+
+ dnl Initialize to the default path.
+ ax_cv_lua_luadir="$LUA_PREFIX/share/lua/$LUA_VERSION"
+
+ dnl Try to find a path with the prefix.
+ _AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_prefix], [package.path])
+ AS_IF([test "x$ax_lua_prefixed_path" != 'x'],
+ [ dnl Fix the prefix.
+ _ax_strip_prefix=`echo "$ax_lua_prefix" | sed 's|.|.|g'`
+ ax_cv_lua_luadir=`echo "$ax_lua_prefixed_path" | \
+ sed "s,^$_ax_strip_prefix,$LUA_PREFIX,"`
+ ])
+ ])
+ AC_SUBST([luadir], [$ax_cv_lua_luadir])
+ AC_SUBST([pkgluadir], [\${luadir}/$PACKAGE])
+
+ dnl Lua provides no way to query the module directory, and instead
+ dnl provides LUA_PATH. However, we should be able to make a safe educated
+ dnl guess. If the built-in search path contains a directory which is
+ dnl prefixed by $exec_prefix, then we can store modules there. The first
+ dnl matching path will be used.
+ AC_CACHE_CHECK([for $ax_display_LUA module directory],
+ [ax_cv_lua_luaexecdir],
+ [ AS_IF([test "x$exec_prefix" = 'xNONE'],
+ [ax_lua_exec_prefix=$ax_lua_prefix],
+ [ax_lua_exec_prefix=$exec_prefix])
+
+ dnl Initialize to the default path.
+ ax_cv_lua_luaexecdir="$LUA_EXEC_PREFIX/lib/lua/$LUA_VERSION"
+
+ dnl Try to find a path with the prefix.
+ _AX_LUA_FND_PRFX_PTH([$LUA],
+ [$ax_lua_exec_prefix], [package.cpathd])
+ AS_IF([test "x$ax_lua_prefixed_path" != 'x'],
+ [ dnl Fix the prefix.
+ _ax_strip_prefix=`echo "$ax_lua_exec_prefix" | sed 's|.|.|g'`
+ ax_cv_lua_luaexecdir=`echo "$ax_lua_prefixed_path" | \
+ sed "s,^$_ax_strip_prefix,$LUA_EXEC_PREFIX,"`
+ ])
+ ])
+ AC_SUBST([luaexecdir], [$ax_cv_lua_luaexecdir])
+ AC_SUBST([pkgluaexecdir], [\${luaexecdir}/$PACKAGE])
+
+ dnl Run any user specified action.
+ $3
+ ])
+])
+
+dnl AX_WITH_LUA is now the same thing as AX_PROG_LUA.
+AC_DEFUN([AX_WITH_LUA],
+[
+ AC_MSG_WARN([[$0 is deprecated, please use AX_PROG_LUA]])
+ AX_PROG_LUA
+])
+
+
+dnl =========================================================================
+dnl _AX_LUA_CHK_IS_INTRP(PROG, [ACTION-IF-TRUE], [ACTION-IF-FALSE])
+dnl =========================================================================
+AC_DEFUN([_AX_LUA_CHK_IS_INTRP],
+[
+ dnl Just print _VERSION because all Lua interpreters have this global.
+ AS_IF([$1 -e "print('Hello ' .. _VERSION .. '!')" &>/dev/null],
+ [$2], [$3])
+])
+
+
+dnl =========================================================================
+dnl _AX_LUA_CHK_VER(PROG, MINIMUM-VERSION, [TOO-BIG-VERSION],
+dnl [ACTION-IF-TRUE], [ACTION-IF-FALSE])
+dnl =========================================================================
+AC_DEFUN([_AX_LUA_CHK_VER],
+[
+ AS_IF([$1 2>/dev/null -e '
+ function norm (v) i,j=v:match "(%d+)%.(%d+)" return 100 * i + j end
+ v=norm (_VERSION)
+ os.exit ((v >= norm ("$2") and ("$3" == "" or v < norm ("$3"))) and 0 or 1)'],
+ [$4], [$5])
+])
+
+
+dnl =========================================================================
+dnl _AX_LUA_FND_PRFX_PTH(PROG, PREFIX, LUA-PATH-VARIABLE)
+dnl =========================================================================
+AC_DEFUN([_AX_LUA_FND_PRFX_PTH],
+[
+ dnl Invokes the Lua interpreter PROG to print the path variable
+ dnl LUA-PATH-VARIABLE, usually package.path or package.cpath. Paths are
+ dnl then matched against PREFIX. The first path to begin with PREFIX is set
+ dnl to ax_lua_prefixed_path.
+
+ ax_lua_prefixed_path=''
+ _ax_package_paths=`$1 -e 'print($3)' 2>/dev/null | sed 's|;|\n|g'`
+ dnl Try the paths in order, looking for the prefix.
+ for _ax_package_path in $_ax_package_paths; do
+ dnl Copy the path, up to the use of a Lua wildcard.
+ _ax_path_parts=`echo "$_ax_package_path" | sed 's|/|\n|g'`
+ _ax_reassembled=''
+ for _ax_path_part in $_ax_path_parts; do
+ echo "$_ax_path_part" | grep '\?' >/dev/null && break
+ _ax_reassembled="$_ax_reassembled/$_ax_path_part"
+ done
+ dnl Check the path against the prefix.
+ _ax_package_path=$_ax_reassembled
+ if echo "$_ax_package_path" | grep "^$2" >/dev/null; then
+ dnl Found it.
+ ax_lua_prefixed_path=$_ax_package_path
+ break
+ fi
+ done
+])
+
+
+dnl =========================================================================
+dnl AX_LUA_HEADERS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl =========================================================================
+AC_DEFUN([AX_LUA_HEADERS],
+[
+ dnl Check for LUA_VERSION.
+ AC_MSG_CHECKING([if LUA_VERSION is defined])
+ AS_IF([test "x$LUA_VERSION" != 'x'],
+ [AC_MSG_RESULT([yes])],
+ [ AC_MSG_RESULT([no])
+ AC_MSG_ERROR([cannot check Lua headers without knowing LUA_VERSION])
+ ])
+
+ dnl Make LUA_INCLUDE a precious variable.
+ AC_ARG_VAR([LUA_INCLUDE], [The Lua includes, e.g. -I/usr/include/lua5.1])
+
+ dnl Some default directories to search.
+ LUA_SHORT_VERSION=`echo "$LUA_VERSION" | sed 's|\.||'`
+ m4_define_default([_AX_LUA_INCLUDE_LIST],
+ [ /usr/include/lua$LUA_VERSION \
+ /usr/include/lua/$LUA_VERSION \
+ /usr/include/lua$LUA_SHORT_VERSION \
+ /usr/local/include/lua$LUA_VERSION \
+ /usr/local/include/lua-$LUA_VERSION \
+ /usr/local/include/lua/$LUA_VERSION \
+ /usr/local/include/lua$LUA_SHORT_VERSION \
+ ])
+
+ dnl Try to find the headers.
+ _ax_lua_saved_cppflags=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $LUA_INCLUDE"
+ AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h])
+ CPPFLAGS=$_ax_lua_saved_cppflags
+
+ dnl Try some other directories if LUA_INCLUDE was not set.
+ AS_IF([test "x$LUA_INCLUDE" = 'x' &&
+ test "x$ac_cv_header_lua_h" != 'xyes'],
+ [ dnl Try some common include paths.
+ for _ax_include_path in _AX_LUA_INCLUDE_LIST; do
+ test ! -d "$_ax_include_path" && continue
+
+ AC_MSG_CHECKING([for Lua headers in])
+ AC_MSG_RESULT([$_ax_include_path])
+
+ AS_UNSET([ac_cv_header_lua_h])
+ AS_UNSET([ac_cv_header_lualib_h])
+ AS_UNSET([ac_cv_header_lauxlib_h])
+ AS_UNSET([ac_cv_header_luaconf_h])
+
+ _ax_lua_saved_cppflags=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS -I$_ax_include_path"
+ AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h])
+ CPPFLAGS=$_ax_lua_saved_cppflags
+
+ AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'],
+ [ LUA_INCLUDE="-I$_ax_include_path"
+ break
+ ])
+ done
+ ])
+
+ AS_IF([test "x$ac_cv_header_lua_h" = 'xyes' && test "x$cross_compiling" != 'xyes'],
+ [ dnl Make a program to print LUA_VERSION defined in the header.
+ dnl TODO This probably shouldn't be a runtime test.
+
+ AC_CACHE_CHECK([for Lua header version],
+ [ax_cv_lua_header_version],
+ [ _ax_lua_saved_cppflags=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $LUA_INCLUDE"
+ AC_RUN_IFELSE(
+ [ AC_LANG_SOURCE([[
+#include <lua.h>
+#include <stdlib.h>
+#include <stdio.h>
+int main(int argc, char ** argv)
+{
+ if(argc > 1) printf("%s", LUA_VERSION);
+ exit(EXIT_SUCCESS);
+}
+]])
+ ],
+ [ ax_cv_lua_header_version=`./conftest$EXEEXT p | \
+ sed "s|^Lua \(.*\)|\1|" | \
+ grep -o "^@<:@0-9@:>@\+\\.@<:@0-9@:>@\+"`
+ ],
+ [ax_cv_lua_header_version='unknown'])
+ CPPFLAGS=$_ax_lua_saved_cppflags
+ ])
+
+ dnl Compare this to the previously found LUA_VERSION.
+ AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION])
+ AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"],
+ [ AC_MSG_RESULT([yes])
+ ax_header_version_match='yes'
+ ],
+ [ AC_MSG_RESULT([no])
+ ax_header_version_match='no'
+ ])
+ ],
+ [
+ ax_header_version_match='yes'
+ ])
+
+ dnl Was LUA_INCLUDE specified?
+ AS_IF([test "x$ax_header_version_match" != 'xyes' &&
+ test "x$LUA_INCLUDE" != 'x'],
+ [AC_MSG_ERROR([cannot find headers for specified LUA_INCLUDE])])
+
+ dnl Test the final result and run user code.
+ AS_IF([test "x$ax_header_version_match" = 'xyes'], [$1],
+ [m4_default([$2], [AC_MSG_ERROR([cannot find Lua includes])])])
+])
+
+dnl AX_LUA_HEADERS_VERSION no longer exists, use AX_LUA_HEADERS.
+AC_DEFUN([AX_LUA_HEADERS_VERSION],
+[
+ AC_MSG_WARN([[$0 is deprecated, please use AX_LUA_HEADERS]])
+])
+
+
+dnl =========================================================================
+dnl AX_LUA_LIBS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl =========================================================================
+AC_DEFUN([AX_LUA_LIBS],
+[
+ dnl TODO Should this macro also check various -L flags?
+
+ dnl Check for LUA_VERSION.
+ AC_MSG_CHECKING([if LUA_VERSION is defined])
+ AS_IF([test "x$LUA_VERSION" != 'x'],
+ [AC_MSG_RESULT([yes])],
+ [ AC_MSG_RESULT([no])
+ AC_MSG_ERROR([cannot check Lua libs without knowing LUA_VERSION])
+ ])
+
+ dnl Make LUA_LIB a precious variable.
+ AC_ARG_VAR([LUA_LIB], [The Lua library, e.g. -llua5.1])
+
+ AS_IF([test "x$LUA_LIB" != 'x'],
+ [ dnl Check that LUA_LIBS works.
+ _ax_lua_saved_libs=$LIBS
+ LIBS="$LIBS $LUA_LIB"
+ AC_SEARCH_LIBS([lua_load], [],
+ [_ax_found_lua_libs='yes'],
+ [_ax_found_lua_libs='no'])
+ LIBS=$_ax_lua_saved_libs
+
+ dnl Check the result.
+ AS_IF([test "x$_ax_found_lua_libs" != 'xyes'],
+ [AC_MSG_ERROR([cannot find libs for specified LUA_LIB])])
+ ],
+ [ dnl First search for extra libs.
+ _ax_lua_extra_libs=''
+
+ _ax_lua_saved_libs=$LIBS
+ LIBS="$LIBS $LUA_LIB"
+ AC_SEARCH_LIBS([exp], [m])
+ AC_SEARCH_LIBS([dlopen], [dl])
+ LIBS=$_ax_lua_saved_libs
+
+ AS_IF([test "x$ac_cv_search_exp" != 'xno' &&
+ test "x$ac_cv_search_exp" != 'xnone required'],
+ [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_exp"])
+
+ AS_IF([test "x$ac_cv_search_dlopen" != 'xno' &&
+ test "x$ac_cv_search_dlopen" != 'xnone required'],
+ [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_dlopen"])
+
+ dnl Try to find the Lua libs.
+ _ax_lua_saved_libs=$LIBS
+ LIBS="$LIBS $LUA_LIB"
+ AC_SEARCH_LIBS([lua_load],
+ [ lua$LUA_VERSION \
+ lua$LUA_SHORT_VERSION \
+ lua-$LUA_VERSION \
+ lua-$LUA_SHORT_VERSION \
+ lua],
+ [_ax_found_lua_libs='yes'],
+ [_ax_found_lua_libs='no'],
+ [$_ax_lua_extra_libs])
+ LIBS=$_ax_lua_saved_libs
+
+ AS_IF([test "x$ac_cv_search_lua_load" != 'xno' &&
+ test "x$ac_cv_search_lua_load" != 'xnone required'],
+ [LUA_LIB="$ac_cv_search_lua_load $_ax_lua_extra_libs"])
+ ])
+
+ dnl Test the result and run user code.
+ AS_IF([test "x$_ax_found_lua_libs" = 'xyes'], [$1],
+ [m4_default([$2], [AC_MSG_ERROR([cannot find Lua libs])])])
+])
+
+
+dnl =========================================================================
+dnl AX_LUA_READLINE([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl =========================================================================
+AC_DEFUN([AX_LUA_READLINE],
+[
+ AX_LIB_READLINE
+ AS_IF([test "x$ac_cv_header_readline_readline_h" != 'x' &&
+ test "x$ac_cv_header_readline_history_h" != 'x'],
+ [ LUA_LIBS_CFLAGS="-DLUA_USE_READLINE $LUA_LIBS_CFLAGS"
+ $1
+ ],
+ [$2])
+])
diff --git a/src/ucl_emitter.c b/src/ucl_emitter.c
index 11fe50c00602..8134d09c1a65 100644
--- a/src/ucl_emitter.c
+++ b/src/ucl_emitter.c
@@ -130,6 +130,19 @@ ucl_emitter_print_key (bool print_key, struct ucl_emitter_context *ctx,
func->ucl_emitter_append_character (' ', 1, func->ud);
}
}
+ else if (ctx->id == UCL_EMIT_YAML) {
+ if (obj->keylen > 0 && (obj->flags & UCL_OBJECT_NEED_KEY_ESCAPE)) {
+ ucl_elt_string_write_json (obj->key, obj->keylen, ctx);
+ }
+ else if (obj->keylen > 0) {
+ func->ucl_emitter_append_len (obj->key, obj->keylen, func->ud);
+ }
+ else {
+ func->ucl_emitter_append_len ("null", 4, func->ud);
+ }
+
+ func->ucl_emitter_append_len (": ", 2, func->ud);
+ }
else {
if (obj->keylen > 0) {
ucl_elt_string_write_json (obj->key, obj->keylen, ctx);
@@ -182,7 +195,7 @@ ucl_emitter_common_end_object (struct ucl_emitter_context *ctx,
const struct ucl_emitter_functions *func = ctx->func;
if (UCL_EMIT_IDENT_TOP_OBJ(ctx, obj)) {
- ctx->ident --;
+ ctx->indent --;
if (compact) {
func->ucl_emitter_append_character ('}', 1, func->ud);
}
@@ -191,7 +204,7 @@ ucl_emitter_common_end_object (struct ucl_emitter_context *ctx,
/* newline is already added for this format */
func->ucl_emitter_append_character ('\n', 1, func->ud);
}
- ucl_add_tabs (func, ctx->ident, compact);
+ ucl_add_tabs (func, ctx->indent, compact);
func->ucl_emitter_append_character ('}', 1, func->ud);
}
}
@@ -210,7 +223,7 @@ ucl_emitter_common_end_array (struct ucl_emitter_context *ctx,
{
const struct ucl_emitter_functions *func = ctx->func;
- ctx->ident --;
+ ctx->indent --;
if (compact) {
func->ucl_emitter_append_character (']', 1, func->ud);
}
@@ -219,7 +232,7 @@ ucl_emitter_common_end_array (struct ucl_emitter_context *ctx,
/* newline is already added for this format */
func->ucl_emitter_append_character ('\n', 1, func->ud);
}
- ucl_add_tabs (func, ctx->ident, compact);
+ ucl_add_tabs (func, ctx->indent, compact);
func->ucl_emitter_append_character (']', 1, func->ud);
}
@@ -249,7 +262,7 @@ ucl_emitter_common_start_array (struct ucl_emitter_context *ctx,
func->ucl_emitter_append_len ("[\n", 2, func->ud);
}
- ctx->ident ++;
+ ctx->indent ++;
if (obj->type == UCL_ARRAY) {
/* explicit array */
@@ -294,7 +307,7 @@ ucl_emitter_common_start_object (struct ucl_emitter_context *ctx,
else {
func->ucl_emitter_append_len ("{\n", 2, func->ud);
}
- ctx->ident ++;
+ ctx->indent ++;
}
while ((cur = ucl_hash_iterate (obj->value.ov, &it))) {
@@ -315,7 +328,7 @@ ucl_emitter_common_start_object (struct ucl_emitter_context *ctx,
func->ucl_emitter_append_len (",\n", 2, func->ud);
}
}
- ucl_add_tabs (func, ctx->ident, compact);
+ ucl_add_tabs (func, ctx->indent, compact);
ucl_emitter_common_start_array (ctx, cur, true, compact);
ucl_emitter_common_end_array (ctx, cur, compact);
}
@@ -342,17 +355,23 @@ ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
{
const struct ucl_emitter_functions *func = ctx->func;
bool flag;
+ struct ucl_object_userdata *ud;
+ const char *ud_out = "";
if (ctx->id != UCL_EMIT_CONFIG && !first) {
if (compact) {
func->ucl_emitter_append_character (',', 1, func->ud);
}
else {
- func->ucl_emitter_append_len (",\n", 2, func->ud);
+ if (ctx->id == UCL_EMIT_YAML && ctx->indent == 0) {
+ func->ucl_emitter_append_len ("\n", 1, func->ud);
+ } else {
+ func->ucl_emitter_append_len (",\n", 2, func->ud);
+ }
}
}
- ucl_add_tabs (func, ctx->ident, compact);
+ ucl_add_tabs (func, ctx->indent, compact);
switch (obj->type) {
case UCL_INT:
@@ -379,7 +398,12 @@ ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
break;
case UCL_STRING:
ucl_emitter_print_key (print_key, ctx, obj, compact);
- ucl_elt_string_write_json (obj->value.sv, obj->len, ctx);
+ if (ctx->id == UCL_EMIT_CONFIG && ucl_maybe_long_string (obj)) {
+ ucl_elt_string_write_multiline (obj->value.sv, obj->len, ctx);
+ }
+ else {
+ ucl_elt_string_write_json (obj->value.sv, obj->len, ctx);
+ }
ucl_emitter_finish_object (ctx, obj, compact, !print_key);
break;
case UCL_NULL:
@@ -396,6 +420,16 @@ ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
ucl_emitter_common_end_array (ctx, obj, compact);
break;
case UCL_USERDATA:
+ ud = (struct ucl_object_userdata *)obj;
+ ucl_emitter_print_key (print_key, ctx, obj, compact);
+ if (ud->emitter) {
+ ud_out = ud->emitter (obj->value.ud);
+ if (ud_out == NULL) {
+ ud_out = "null";
+ }
+ }
+ ucl_elt_string_write_json (ud_out, strlen (ud_out), ctx);
+ ucl_emitter_finish_object (ctx, obj, compact, !print_key);
break;
}
}
@@ -425,10 +459,10 @@ ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
ucl_emitter_common_end_array (ctx, obj, (compact)); \
}
-UCL_EMIT_TYPE_IMPL(json, false);
-UCL_EMIT_TYPE_IMPL(json_compact, true);
-UCL_EMIT_TYPE_IMPL(config, false);
-UCL_EMIT_TYPE_IMPL(yaml, false);
+UCL_EMIT_TYPE_IMPL(json, false)
+UCL_EMIT_TYPE_IMPL(json_compact, true)
+UCL_EMIT_TYPE_IMPL(config, false)
+UCL_EMIT_TYPE_IMPL(yaml, false)
unsigned char *
ucl_object_emit (const ucl_object_t *obj, enum ucl_emitter emit_type)
@@ -461,7 +495,7 @@ ucl_object_emit_full (const ucl_object_t *obj, enum ucl_emitter emit_type,
if (ctx != NULL) {
memcpy (&my_ctx, ctx, sizeof (my_ctx));
my_ctx.func = emitter;
- my_ctx.ident = 0;
+ my_ctx.indent = 0;
my_ctx.top = obj;
my_ctx.ops->ucl_emitter_write_elt (&my_ctx, obj, true, false);
diff --git a/src/ucl_emitter_streamline.c b/src/ucl_emitter_streamline.c
index acf4a305a46e..ff27c88244a5 100644
--- a/src/ucl_emitter_streamline.c
+++ b/src/ucl_emitter_streamline.c
@@ -108,9 +108,8 @@ ucl_object_emit_streamline_start_container (struct ucl_emitter_context *ctx,
st->is_array = false;
sctx->ops->ucl_emitter_start_object (ctx, obj, print_key);
}
+ LL_PREPEND (sctx->containers, st);
}
-
- LL_PREPEND (sctx->containers, st);
}
void
diff --git a/src/ucl_emitter_utils.c b/src/ucl_emitter_utils.c
index c619ab9df15e..da412097ffbc 100644
--- a/src/ucl_emitter_utils.c
+++ b/src/ucl_emitter_utils.c
@@ -93,9 +93,7 @@ ucl_elt_string_write_json (const char *str, size_t size,
size_t len = 0;
const struct ucl_emitter_functions *func = ctx->func;
- if (ctx->id != UCL_EMIT_YAML) {
- func->ucl_emitter_append_character ('"', 1, func->ud);
- }
+ func->ucl_emitter_append_character ('"', 1, func->ud);
while (size) {
if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
@@ -137,9 +135,18 @@ ucl_elt_string_write_json (const char *str, size_t size,
if (len > 0) {
func->ucl_emitter_append_len (c, len, func->ud);
}
- if (ctx->id != UCL_EMIT_YAML) {
- func->ucl_emitter_append_character ('"', 1, func->ud);
- }
+ func->ucl_emitter_append_character ('"', 1, func->ud);
+}
+
+void
+ucl_elt_string_write_multiline (const char *str, size_t size,
+ struct ucl_emitter_context *ctx)
+{
+ const struct ucl_emitter_functions *func = ctx->func;
+
+ func->ucl_emitter_append_len ("<<EOD\n", sizeof ("<<EOD\n") - 1, func->ud);
+ func->ucl_emitter_append_len (str, size, func->ud);
+ func->ucl_emitter_append_len ("\nEOD", sizeof ("\nEOD") - 1, func->ud);
}
/*
@@ -154,7 +161,7 @@ ucl_utstring_append_character (unsigned char c, size_t len, void *ud)
utstring_append_c (buf, c);
}
else {
- utstring_reserve (buf, len);
+ utstring_reserve (buf, len + 1);
memset (&buf->d[buf->i], c, len);
buf->i += len;
buf->d[buf->i] = '\0';
@@ -267,19 +274,23 @@ ucl_fd_append_character (unsigned char c, size_t len, void *ud)
unsigned char *buf;
if (len == 1) {
- write (fd, &c, 1);
+ return write (fd, &c, 1);
}
else {
buf = malloc (len);
if (buf == NULL) {
/* Fallback */
while (len --) {
- write (fd, &c, 1);
+ if (write (fd, &c, 1) == -1) {
+ return -1;
+ }
}
}
else {
memset (buf, c, len);
- write (fd, buf, len);
+ if (write (fd, buf, len) == -1) {
+ return -1;
+ }
free (buf);
}
}
@@ -292,9 +303,7 @@ ucl_fd_append_len (const unsigned char *str, size_t len, void *ud)
{
int fd = *(int *)ud;
- write (fd, str, len);
-
- return 0;
+ return write (fd, str, len);
}
static int
@@ -304,9 +313,7 @@ ucl_fd_append_int (int64_t val, void *ud)
char intbuf[64];
snprintf (intbuf, sizeof (intbuf), "%jd", (intmax_t)val);
- write (fd, intbuf, strlen (intbuf));
-
- return 0;
+ return write (fd, intbuf, strlen (intbuf));
}
static int
@@ -327,9 +334,7 @@ ucl_fd_append_double (double val, void *ud)
snprintf (nbuf, sizeof (nbuf), "%lf", val);
}
- write (fd, nbuf, strlen (nbuf));
-
- return 0;
+ return write (fd, nbuf, strlen (nbuf));
}
struct ucl_emitter_functions*
@@ -464,3 +469,18 @@ ucl_object_emit_single_json (const ucl_object_t *obj)
return res;
}
+
+#define LONG_STRING_LIMIT 80
+
+bool
+ucl_maybe_long_string (const ucl_object_t *obj)
+{
+ if (obj->len > LONG_STRING_LIMIT || (obj->flags & UCL_OBJECT_MULTILINE)) {
+ /* String is long enough, so search for newline characters in it */
+ if (memchr (obj->value.sv, '\n', obj->len) != NULL) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/ucl_hash.c b/src/ucl_hash.c
index c2e80cb4b252..ea55491ed504 100644
--- a/src/ucl_hash.c
+++ b/src/ucl_hash.c
@@ -66,6 +66,20 @@ ucl_hash_insert (ucl_hash_t* hashlin, const ucl_object_t *obj,
HASH_ADD_KEYPTR (hh, hashlin->buckets, key, keylen, node);
}
+void ucl_hash_replace (ucl_hash_t* hashlin, const ucl_object_t *old,
+ const ucl_object_t *new)
+{
+ ucl_hash_node_t *node;
+
+ HASH_FIND (hh, hashlin->buckets, old->key, old->keylen, node);
+ if (node != NULL) {
+ /* Direct replacement */
+ node->data = new;
+ node->hh.key = new->key;
+ node->hh.keylen = new->keylen;
+ }
+}
+
const void*
ucl_hash_iterate (ucl_hash_t *hashlin, ucl_hash_iter_t *iter)
{
@@ -122,5 +136,6 @@ ucl_hash_delete (ucl_hash_t* hashlin, const ucl_object_t *obj)
if (found) {
HASH_DELETE (hh, hashlin->buckets, found);
+ UCL_FREE (sizeof (ucl_hash_node_t), found);
}
}
diff --git a/src/ucl_hash.h b/src/ucl_hash.h
index cbbf005240bd..ddbfdba19ce8 100644
--- a/src/ucl_hash.h
+++ b/src/ucl_hash.h
@@ -66,6 +66,12 @@ void ucl_hash_insert (ucl_hash_t* hashlin, const ucl_object_t *obj, const char *
unsigned keylen);
/**
+ * Replace element in the hash
+ */
+void ucl_hash_replace (ucl_hash_t* hashlin, const ucl_object_t *old,
+ const ucl_object_t *new);
+
+/**
* Delete an element from the the hashtable.
*/
void ucl_hash_delete (ucl_hash_t* hashlin, const ucl_object_t *obj);
diff --git a/src/ucl_internal.h b/src/ucl_internal.h
index 0ac8de84c2ed..2f75872b7bb8 100644
--- a/src/ucl_internal.h
+++ b/src/ucl_internal.h
@@ -163,6 +163,7 @@ struct ucl_chunk {
size_t remain;
unsigned int line;
unsigned int column;
+ unsigned priority;
struct ucl_chunk *next;
};
@@ -182,7 +183,7 @@ struct ucl_variable {
char *value;
size_t var_len;
size_t value_len;
- struct ucl_variable *next;
+ struct ucl_variable *prev, *next;
};
struct ucl_parser {
@@ -192,6 +193,7 @@ struct ucl_parser {
int flags;
ucl_object_t *top_obj;
ucl_object_t *cur_obj;
+ char *cur_file;
struct ucl_macro *macroes;
struct ucl_stack *stack;
struct ucl_chunk *chunks;
@@ -202,6 +204,12 @@ struct ucl_parser {
UT_string *err;
};
+struct ucl_object_userdata {
+ ucl_object_t obj;
+ ucl_userdata_dtor dtor;
+ ucl_userdata_emitter emitter;
+};
+
/**
* Unescape json string inplace
* @param str
@@ -216,9 +224,11 @@ size_t ucl_unescape_json_string (char *str, size_t len);
* @param err error ptr
* @return
*/
-bool ucl_include_handler (const unsigned char *data, size_t len, void* ud);
+bool ucl_include_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud);
-bool ucl_try_include_handler (const unsigned char *data, size_t len, void* ud);
+bool ucl_try_include_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud);
/**
* Handle includes macro
@@ -228,7 +238,8 @@ bool ucl_try_include_handler (const unsigned char *data, size_t len, void* ud);
* @param err error ptr
* @return
*/
-bool ucl_includes_handler (const unsigned char *data, size_t len, void* ud);
+bool ucl_includes_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud);
size_t ucl_strlcpy (char *dst, const char *src, size_t siz);
size_t ucl_strlcpy_unsafe (char *dst, const char *src, size_t siz);
@@ -264,7 +275,7 @@ ucl_create_err (UT_string **err, const char *fmt, ...)
static inline bool
ucl_maybe_parse_boolean (ucl_object_t *obj, const unsigned char *start, size_t len)
{
- const unsigned char *p = start;
+ const char *p = (const char *)start;
bool ret = false, val = false;
if (len == 5) {
@@ -351,7 +362,7 @@ const struct ucl_emitter_context *
ucl_emit_get_standard_context (enum ucl_emitter emit_type);
/**
- * Serialise string
+ * Serialize string as JSON string
* @param str string to emit
* @param buf target buffer
*/
@@ -359,10 +370,27 @@ void ucl_elt_string_write_json (const char *str, size_t size,
struct ucl_emitter_context *ctx);
/**
+ * Write multiline string using `EOD` as string terminator
+ * @param str
+ * @param size
+ * @param ctx
+ */
+void ucl_elt_string_write_multiline (const char *str, size_t size,
+ struct ucl_emitter_context *ctx);
+
+/**
* Emit a single object to string
* @param obj
* @return
*/
unsigned char * ucl_object_emit_single_json (const ucl_object_t *obj);
+/**
+ * Check whether a specified string is long and should be likely printed in
+ * multiline mode
+ * @param obj
+ * @return
+ */
+bool ucl_maybe_long_string (const ucl_object_t *obj);
+
#endif /* UCL_INTERNAL_H_ */
diff --git a/src/ucl_parser.c b/src/ucl_parser.c
index e51a54b77bc6..0d118d84b75a 100644
--- a/src/ucl_parser.c
+++ b/src/ucl_parser.c
@@ -26,8 +26,8 @@
#include "ucl_chartable.h"
/**
- * @file rcl_parser.c
- * The implementation of rcl parser
+ * @file ucl_parser.c
+ * The implementation of ucl parser
*/
struct ucl_parser_saved_state {
@@ -56,20 +56,33 @@ struct ucl_parser_saved_state {
} while (0)
static inline void
-ucl_set_err (struct ucl_chunk *chunk, int code, const char *str, UT_string **err)
+ucl_set_err (struct ucl_parser *parser, int code, const char *str, UT_string **err)
{
+ const char *fmt_string, *filename;
+ struct ucl_chunk *chunk = parser->chunks;
+
+ if (parser->cur_file) {
+ filename = parser->cur_file;
+ }
+ else {
+ filename = "<unknown>";
+ }
if (chunk->pos < chunk->end) {
if (isgraph (*chunk->pos)) {
- ucl_create_err (err, "error on line %d at column %d: '%s', character: '%c'",
- chunk->line, chunk->column, str, *chunk->pos);
+ fmt_string = "error while parsing %s: "
+ "line: %d, column: %d - '%s', character: '%c'";
}
else {
- ucl_create_err (err, "error on line %d at column %d: '%s', character: '0x%02x'",
- chunk->line, chunk->column, str, (int)*chunk->pos);
+ fmt_string = "error while parsing %s: "
+ "line: %d, column: %d - '%s', character: '0x%02x'";
}
+ ucl_create_err (err, fmt_string,
+ filename, chunk->line, chunk->column,
+ str, *chunk->pos);
}
else {
- ucl_create_err (err, "error at the end of chunk: %s", str);
+ ucl_create_err (err, "error while parsing %s: at the end of chunk: %s",
+ filename, str);
}
}
@@ -84,11 +97,12 @@ ucl_skip_comments (struct ucl_parser *parser)
struct ucl_chunk *chunk = parser->chunks;
const unsigned char *p;
int comments_nested = 0;
+ bool quoted = false;
p = chunk->pos;
start:
- if (*p == '#') {
+ if (chunk->remain > 0 && *p == '#') {
if (parser->state != UCL_STATE_SCOMMENT &&
parser->state != UCL_STATE_MCOMMENT) {
while (p < chunk->end) {
@@ -100,34 +114,41 @@ start:
}
}
}
- else if (*p == '/' && chunk->remain >= 2) {
+ else if (chunk->remain >= 2 && *p == '/') {
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;
+ if (*p == '"' && *(p - 1) != '\\') {
+ quoted = !quoted;
+ }
+
+ if (!quoted) {
+ 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);
- }
- else if (p[0] == '/' && chunk->remain >= 2 && p[1] == '*') {
- comments_nested ++;
- ucl_chunk_skipc (chunk, p);
- ucl_chunk_skipc (chunk, p);
- continue;
}
ucl_chunk_skipc (chunk, p);
}
if (comments_nested != 0) {
- ucl_set_err (chunk, UCL_ENESTED, "unfinished multiline comment", &parser->err);
+ ucl_set_err (parser, UCL_ENESTED,
+ "unfinished multiline comment", &parser->err);
return false;
}
}
@@ -492,7 +513,8 @@ ucl_copy_or_store_ptr (struct ucl_parser *parser,
/* Copy string */
*dst = UCL_ALLOC (in_len + 1);
if (*dst == NULL) {
- ucl_set_err (parser->chunks, 0, "cannot allocate memory for a string", &parser->err);
+ ucl_set_err (parser, 0, "cannot allocate memory for a string",
+ &parser->err);
return false;
}
if (need_lowercase) {
@@ -514,6 +536,10 @@ ucl_copy_or_store_ptr (struct ucl_parser *parser,
*dst = tmp;
ret = tret;
}
+ else {
+ /* Free unexpanded value */
+ UCL_FREE (in_len + 1, tmp);
+ }
}
*dst_const = *dst;
}
@@ -539,7 +565,7 @@ ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_arra
if (!is_array) {
if (obj == NULL) {
- obj = ucl_object_typed_new (UCL_OBJECT);
+ obj = ucl_object_new_full (UCL_OBJECT, parser->chunks->priority);
}
else {
obj->type = UCL_OBJECT;
@@ -549,7 +575,7 @@ ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_arra
}
else {
if (obj == NULL) {
- obj = ucl_object_typed_new (UCL_ARRAY);
+ obj = ucl_object_new_full (UCL_ARRAY, parser->chunks->priority);
}
else {
obj->type = UCL_ARRAY;
@@ -559,7 +585,9 @@ ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_arra
st = UCL_ALLOC (sizeof (struct ucl_stack));
if (st == NULL) {
- ucl_set_err (parser->chunks, 0, "cannot allocate memory for an object", &parser->err);
+ ucl_set_err (parser, 0, "cannot allocate memory for an object",
+ &parser->err);
+ ucl_object_unref (obj);
return NULL;
}
st->obj = obj;
@@ -676,8 +704,7 @@ ucl_maybe_parse_number (ucl_object_t *obj,
}
/* Now check endptr */
- if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0' ||
- ucl_test_character (*endptr, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0') {
p = endptr;
goto set_obj;
}
@@ -788,8 +815,21 @@ ucl_maybe_parse_number (ucl_object_t *obj,
goto set_obj;
}
break;
+ case '\t':
+ case ' ':
+ while (p < end && ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
+ p++;
+ }
+ if (ucl_lex_is_atom_end(*p))
+ goto set_obj;
+ break;
}
}
+ else if (endptr == end) {
+ /* Just a number at the end of chunk */
+ p = endptr;
+ goto set_obj;
+ }
*pos = c;
return EINVAL;
@@ -835,7 +875,7 @@ ucl_lex_number (struct ucl_parser *parser,
return true;
}
else if (ret == ERANGE) {
- ucl_set_err (chunk, ERANGE, "numeric value out of range", &parser->err);
+ ucl_set_err (parser, ERANGE, "numeric value out of range", &parser->err);
}
return false;
@@ -860,10 +900,12 @@ ucl_lex_json_string (struct ucl_parser *parser,
if (c < 0x1F) {
/* Unmasked control character */
if (c == '\n') {
- ucl_set_err (chunk, UCL_ESYNTAX, "unexpected newline", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "unexpected newline",
+ &parser->err);
}
else {
- ucl_set_err (chunk, UCL_ESYNTAX, "unexpected control character", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "unexpected control character",
+ &parser->err);
}
return false;
}
@@ -871,7 +913,8 @@ ucl_lex_json_string (struct ucl_parser *parser,
ucl_chunk_skipc (chunk, p);
c = *p;
if (p >= chunk->end) {
- ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "unfinished escape character",
+ &parser->err);
return false;
}
else if (ucl_test_character (c, UCL_CHARACTER_ESCAPE)) {
@@ -879,13 +922,15 @@ ucl_lex_json_string (struct ucl_parser *parser,
ucl_chunk_skipc (chunk, p);
for (i = 0; i < 4 && p < chunk->end; i ++) {
if (!isxdigit (*p)) {
- ucl_set_err (chunk, UCL_ESYNTAX, "invalid utf escape", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "invalid utf escape",
+ &parser->err);
return false;
}
ucl_chunk_skipc (chunk, p);
}
if (p >= chunk->end) {
- ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "unfinished escape character",
+ &parser->err);
return false;
}
}
@@ -910,10 +955,42 @@ ucl_lex_json_string (struct ucl_parser *parser,
ucl_chunk_skipc (chunk, p);
}
- ucl_set_err (chunk, UCL_ESYNTAX, "no quote at the end of json string", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "no quote at the end of json string",
+ &parser->err);
return false;
}
+static void
+ucl_parser_append_elt (struct ucl_parser *parser, ucl_hash_t *cont,
+ ucl_object_t *top,
+ ucl_object_t *elt)
+{
+ ucl_object_t *nobj;
+
+ if ((parser->flags & UCL_PARSER_NO_IMPLICIT_ARRAYS) == 0) {
+ /* Implicit array */
+ top->flags |= UCL_OBJECT_MULTIVALUE;
+ DL_APPEND (top, elt);
+ }
+ else {
+ if ((top->flags & UCL_OBJECT_MULTIVALUE) != 0) {
+ /* Just add to the explicit array */
+ DL_APPEND (top->value.av, elt);
+ }
+ else {
+ /* Convert to an array */
+ ucl_hash_delete (cont, top);
+ nobj = ucl_object_typed_new (UCL_ARRAY);
+ nobj->key = top->key;
+ nobj->keylen = top->keylen;
+ nobj->flags |= UCL_OBJECT_MULTIVALUE;
+ DL_APPEND (nobj->value.av, top);
+ DL_APPEND (nobj->value.av, elt);
+ ucl_hash_insert (cont, nobj, nobj->key, nobj->keylen);
+ }
+ }
+}
+
/**
* Parse a key in an object
* @param parser
@@ -981,7 +1058,8 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
}
else {
/* Invalid identifier */
- ucl_set_err (chunk, UCL_ESYNTAX, "key must begin with a letter", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "key must begin with a letter",
+ &parser->err);
return false;
}
}
@@ -997,7 +1075,8 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
break;
}
else {
- ucl_set_err (chunk, UCL_ESYNTAX, "invalid character in a key", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "invalid character in a key",
+ &parser->err);
return false;
}
}
@@ -1015,7 +1094,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
}
if (p >= chunk->end && got_content) {
- ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "unfinished key", &parser->err);
return false;
}
else if (!got_content) {
@@ -1033,7 +1112,8 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
got_eq = true;
}
else {
- ucl_set_err (chunk, UCL_ESYNTAX, "unexpected '=' character", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "unexpected '=' character",
+ &parser->err);
return false;
}
}
@@ -1043,7 +1123,8 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
got_semicolon = true;
}
else {
- ucl_set_err (chunk, UCL_ESYNTAX, "unexpected ':' character", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "unexpected ':' character",
+ &parser->err);
return false;
}
}
@@ -1061,7 +1142,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
}
if (p >= chunk->end && got_content) {
- ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "unfinished key", &parser->err);
return false;
}
@@ -1096,7 +1177,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
}
/* Create a new object */
- nobj = ucl_object_new ();
+ nobj = ucl_object_new_full (UCL_NULL, parser->chunks->priority);
keylen = ucl_copy_or_store_ptr (parser, c, &nobj->trash_stack[UCL_TRASH_KEY],
&key, end - c, need_unescape, parser->flags & UCL_PARSER_KEY_LOWERCASE, false);
if (keylen == -1) {
@@ -1104,7 +1185,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
return false;
}
else if (keylen == 0) {
- ucl_set_err (chunk, UCL_ESYNTAX, "empty keys are not allowed", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "empty keys are not allowed", &parser->err);
ucl_object_unref (nobj);
return false;
}
@@ -1120,7 +1201,27 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
parser->stack->obj->len ++;
}
else {
- DL_APPEND (tobj, nobj);
+ /*
+ * The logic here is the following:
+ *
+ * - if we have two objects with the same priority, then we form an
+ * implicit or explicit array
+ * - if a new object has bigger priority, then we overwrite an old one
+ * - if a new object has lower priority, then we ignore it
+ */
+ unsigned priold = ucl_object_get_priority (tobj),
+ prinew = ucl_object_get_priority (nobj);
+ if (priold == prinew) {
+ ucl_parser_append_elt (parser, container, tobj, nobj);
+ }
+ else if (priold > prinew) {
+ ucl_object_unref (nobj);
+ return true;
+ }
+ else {
+ ucl_hash_replace (container, tobj, nobj);
+ ucl_object_unref (tobj);
+ }
}
if (ucl_escape) {
@@ -1197,11 +1298,6 @@ ucl_parse_string_value (struct ucl_parser *parser,
ucl_chunk_skipc (chunk, p);
}
- if (p >= chunk->end) {
- ucl_set_err (chunk, UCL_ESYNTAX, "unfinished value", &parser->err);
- return false;
- }
-
return true;
}
@@ -1219,7 +1315,7 @@ ucl_parse_multiline_string (struct ucl_parser *parser,
int term_len, unsigned char const **beg,
bool *var_expand)
{
- const unsigned char *p, *c;
+ const unsigned char *p, *c, *tend;
bool newline = false;
int len = 0;
@@ -1232,7 +1328,13 @@ ucl_parse_multiline_string (struct ucl_parser *parser,
if (chunk->end - p < term_len) {
return 0;
}
- else if (memcmp (p, term, term_len) == 0 && (p[term_len] == '\n' || p[term_len] == '\r')) {
+ else if (memcmp (p, term, term_len) == 0) {
+ tend = p + term_len;
+ if (*tend != '\n' && *tend != ';' && *tend != ',') {
+ /* Incomplete terminator */
+ ucl_chunk_skipc (chunk, p);
+ continue;
+ }
len = p - c;
chunk->remain -= term_len;
chunk->pos = p + term_len;
@@ -1263,7 +1365,7 @@ ucl_get_value_object (struct ucl_parser *parser)
if (parser->stack->obj->type == UCL_ARRAY) {
/* Object must be allocated */
- obj = ucl_object_new ();
+ obj = ucl_object_new_full (UCL_NULL, parser->chunks->priority);
t = parser->stack->obj->value.av;
DL_APPEND (t, obj);
parser->cur_obj = obj;
@@ -1378,7 +1480,8 @@ ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
chunk->line ++;
if ((str_len = ucl_parse_multiline_string (parser, chunk, c,
p - c, &c, &var_expand)) == 0) {
- ucl_set_err (chunk, UCL_ESYNTAX, "unterminated multiline value", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX,
+ "unterminated multiline value", &parser->err);
return false;
}
obj->type = UCL_STRING;
@@ -1423,7 +1526,8 @@ parse_string:
}
str_len = chunk->pos - c - stripped_spaces;
if (str_len <= 0) {
- ucl_set_err (chunk, 0, "string value must not be empty", &parser->err);
+ ucl_set_err (parser, 0, "string value must not be empty",
+ &parser->err);
return false;
}
else if (str_len == 4 && memcmp (c, "null", 4) == 0) {
@@ -1482,7 +1586,9 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
else if (ucl_test_character (*p, UCL_CHARACTER_VALUE_END)) {
if (*p == '}' || *p == ']') {
if (parser->stack == NULL) {
- ucl_set_err (chunk, UCL_ESYNTAX, "end of array or object detected without corresponding start", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX,
+ "end of array or object detected without corresponding start",
+ &parser->err);
return false;
}
if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) ||
@@ -1503,7 +1609,9 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
}
}
else {
- ucl_set_err (chunk, UCL_ESYNTAX, "unexpected terminating symbol detected", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX,
+ "unexpected terminating symbol detected",
+ &parser->err);
return false;
}
@@ -1525,7 +1633,8 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
else {
/* Anything else */
if (!got_sep) {
- ucl_set_err (chunk, UCL_ESYNTAX, "delimiter is missing", &parser->err);
+ ucl_set_err (parser, UCL_ESYNTAX, "delimiter is missing",
+ &parser->err);
return false;
}
return true;
@@ -1613,6 +1722,120 @@ ucl_parse_macro_value (struct ucl_parser *parser,
}
/**
+ * Parse macro arguments as UCL object
+ * @param parser parser structure
+ * @param chunk the current data chunk
+ * @return
+ */
+static ucl_object_t *
+ucl_parse_macro_arguments (struct ucl_parser *parser,
+ struct ucl_chunk *chunk)
+{
+ ucl_object_t *res = NULL;
+ struct ucl_parser *params_parser;
+ int obraces = 1, ebraces = 0, state = 0;
+ const unsigned char *p, *c;
+ size_t args_len = 0;
+ struct ucl_parser_saved_state saved;
+
+ saved.column = chunk->column;
+ saved.line = chunk->line;
+ saved.pos = chunk->pos;
+ saved.remain = chunk->remain;
+ p = chunk->pos;
+
+ if (*p != '(' || chunk->remain < 2) {
+ return NULL;
+ }
+
+ /* Set begin and start */
+ ucl_chunk_skipc (chunk, p);
+ c = p;
+
+ while ((p) < (chunk)->end) {
+ switch (state) {
+ case 0:
+ /* Parse symbols and check for '(', ')' and '"' */
+ if (*p == '(') {
+ obraces ++;
+ }
+ else if (*p == ')') {
+ ebraces ++;
+ }
+ else if (*p == '"') {
+ state = 1;
+ }
+ /* Check pairing */
+ if (obraces == ebraces) {
+ state = 99;
+ }
+ else {
+ args_len ++;
+ }
+ /* Check overflow */
+ if (chunk->remain == 0) {
+ goto restore_chunk;
+ }
+ ucl_chunk_skipc (chunk, p);
+ break;
+ case 1:
+ /* We have quote character, so skip all but quotes */
+ if (*p == '"' && *(p - 1) != '\\') {
+ state = 0;
+ }
+ if (chunk->remain == 0) {
+ goto restore_chunk;
+ }
+ ucl_chunk_skipc (chunk, p);
+ break;
+ case 99:
+ /*
+ * We have read the full body of arguments, so we need to parse and set
+ * object from that
+ */
+ params_parser = ucl_parser_new (parser->flags);
+ if (!ucl_parser_add_chunk (params_parser, c, args_len)) {
+ ucl_set_err (parser, UCL_ESYNTAX, "macro arguments parsing error",
+ &parser->err);
+ }
+ else {
+ res = ucl_parser_get_object (params_parser);
+ }
+ ucl_parser_free (params_parser);
+
+ return res;
+
+ break;
+ }
+ }
+
+ return res;
+
+restore_chunk:
+ chunk->column = saved.column;
+ chunk->line = saved.line;
+ chunk->pos = saved.pos;
+ chunk->remain = saved.remain;
+
+ return NULL;
+}
+
+#define SKIP_SPACES_COMMENTS(parser, chunk, p) do { \
+ while ((p) < (chunk)->end) { \
+ if (!ucl_test_character (*(p), UCL_CHARACTER_WHITESPACE_UNSAFE)) { \
+ if ((chunk)->remain >= 2 && ucl_lex_is_comment ((p)[0], (p)[1])) { \
+ if (!ucl_skip_comments (parser)) { \
+ return false; \
+ } \
+ p = (chunk)->pos; \
+ } \
+ break; \
+ } \
+ ucl_chunk_skipc (chunk, p); \
+ } \
+} while(0)
+
+/**
* Handle the main states of rcl parser
* @param parser parser structure
* @param data the pointer to the beginning of a chunk
@@ -1622,13 +1845,13 @@ ucl_parse_macro_value (struct ucl_parser *parser,
static bool
ucl_state_machine (struct ucl_parser *parser)
{
- ucl_object_t *obj;
+ ucl_object_t *obj, *macro_args;
struct ucl_chunk *chunk = parser->chunks;
const unsigned char *p, *c = NULL, *macro_start = NULL;
unsigned char *macro_escaped;
size_t macro_len = 0;
struct ucl_macro *macro = NULL;
- bool next_key = false, end_of_object = false;
+ bool next_key = false, end_of_object = false, ret;
if (parser->top_obj == NULL) {
if (*chunk->pos == '[') {
@@ -1654,7 +1877,6 @@ ucl_state_machine (struct ucl_parser *parser)
* if we got [ or { correspondingly or can just treat new data as
* a key of newly created object
*/
- obj = parser->cur_obj;
if (!ucl_skip_comments (parser)) {
parser->prev_state = parser->state;
parser->state = UCL_STATE_ERROR;
@@ -1691,7 +1913,7 @@ ucl_state_machine (struct ucl_parser *parser)
}
if (parser->stack == NULL) {
/* No objects are on stack, but we want to parse a key */
- ucl_set_err (chunk, UCL_ESYNTAX, "top object is finished but the parser "
+ ucl_set_err (parser, UCL_ESYNTAX, "top object is finished but the parser "
"expects a key", &parser->err);
parser->prev_state = parser->state;
parser->state = UCL_STATE_ERROR;
@@ -1757,7 +1979,8 @@ ucl_state_machine (struct ucl_parser *parser)
p = chunk->pos;
break;
case UCL_STATE_MACRO_NAME:
- if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
+ if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) &&
+ *p != '(') {
ucl_chunk_skipc (chunk, p);
}
else if (p - c > 0) {
@@ -1772,48 +1995,51 @@ ucl_state_machine (struct ucl_parser *parser)
return false;
}
/* Now we need to skip all spaces */
- while (p < chunk->end) {
- if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
- if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
- /* Skip comment */
- if (!ucl_skip_comments (parser)) {
- return false;
- }
- p = chunk->pos;
- }
- break;
- }
- ucl_chunk_skipc (chunk, p);
- }
+ SKIP_SPACES_COMMENTS(parser, chunk, p);
parser->state = UCL_STATE_MACRO;
}
break;
case UCL_STATE_MACRO:
+ if (*chunk->pos == '(') {
+ macro_args = ucl_parse_macro_arguments (parser, chunk);
+ p = chunk->pos;
+ if (macro_args) {
+ SKIP_SPACES_COMMENTS(parser, chunk, p);
+ }
+ }
+ else {
+ macro_args = NULL;
+ }
if (!ucl_parse_macro_value (parser, chunk, macro,
&macro_start, &macro_len)) {
parser->prev_state = parser->state;
parser->state = UCL_STATE_ERROR;
return false;
}
- macro_len = ucl_expand_variable (parser, &macro_escaped, macro_start, macro_len);
+ macro_len = ucl_expand_variable (parser, &macro_escaped,
+ macro_start, macro_len);
parser->state = parser->prev_state;
if (macro_escaped == NULL) {
- if (!macro->handler (macro_start, macro_len, macro->ud)) {
- return false;
- }
+ ret = macro->handler (macro_start, macro_len, macro_args,
+ macro->ud);
}
else {
- if (!macro->handler (macro_escaped, macro_len, macro->ud)) {
- UCL_FREE (macro_len + 1, macro_escaped);
- return false;
- }
+ ret = macro->handler (macro_escaped, macro_len, macro_args,
+ macro->ud);
UCL_FREE (macro_len + 1, macro_escaped);
}
p = chunk->pos;
+ if (macro_args) {
+ ucl_object_unref (macro_args);
+ }
+ if (!ret) {
+ return false;
+ }
break;
default:
/* TODO: add all states */
- ucl_set_err (chunk, UCL_EINTERNAL, "internal error: parser is in an unknown state", &parser->err);
+ ucl_set_err (parser, UCL_EINTERNAL,
+ "internal error: parser is in an unknown state", &parser->err);
parser->state = UCL_STATE_ERROR;
return false;
}
@@ -1888,7 +2114,7 @@ ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
if (new != NULL) {
/* Remove variable */
- LL_DELETE (parser->variables, new);
+ DL_DELETE (parser->variables, new);
free (new->var);
free (new->value);
UCL_FREE (sizeof (struct ucl_variable), new);
@@ -1910,7 +2136,7 @@ ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
new->value = strdup (value);
new->value_len = strlen (value);
- LL_PREPEND (parser->variables, new);
+ DL_APPEND (parser->variables, new);
}
else {
free (new->value);
@@ -1929,15 +2155,19 @@ ucl_parser_set_variables_handler (struct ucl_parser *parser,
}
bool
-ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
- size_t len)
+ucl_parser_add_chunk_priority (struct ucl_parser *parser, const unsigned char *data,
+ size_t len, unsigned priority)
{
struct ucl_chunk *chunk;
- if (data == NULL || len == 0) {
+ if (data == NULL) {
ucl_create_err (&parser->err, "invalid chunk added");
return false;
}
+ if (len == 0) {
+ parser->top_obj = ucl_object_new_full (UCL_OBJECT, priority);
+ return true;
+ }
if (parser->state != UCL_STATE_ERROR) {
chunk = UCL_ALLOC (sizeof (struct ucl_chunk));
if (chunk == NULL) {
@@ -1950,6 +2180,7 @@ ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
chunk->end = chunk->begin + len;
chunk->line = 1;
chunk->column = 0;
+ chunk->priority = priority;
LL_PREPEND (parser->chunks, chunk);
parser->recursion ++;
if (parser->recursion > UCL_MAX_RECURSION) {
@@ -1966,6 +2197,13 @@ ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
}
bool
+ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
+ size_t len)
+{
+ return ucl_parser_add_chunk_priority (parser, data, len, 0);
+}
+
+bool
ucl_parser_add_string (struct ucl_parser *parser, const char *data,
size_t len)
{
diff --git a/src/ucl_util.c b/src/ucl_util.c
index 63f5e629826f..41702e941f5b 100644
--- a/src/ucl_util.c
+++ b/src/ucl_util.c
@@ -25,6 +25,8 @@
#include "ucl_internal.h"
#include "ucl_chartable.h"
+#include <glob.h>
+
#ifdef HAVE_LIBGEN_H
#include <libgen.h> /* For dirname */
#endif
@@ -129,11 +131,6 @@ static char* ucl_realpath(const char *path, char *resolved_path) {
#define ucl_realpath realpath
#endif
-/**
- * @file rcl_util.c
- * Utilities for rcl parsing
- */
-
typedef void (*ucl_object_dtor) (ucl_object_t *obj);
static void ucl_object_free_internal (ucl_object_t *obj, bool allow_rec,
ucl_object_dtor dtor);
@@ -148,7 +145,19 @@ ucl_object_dtor_free (ucl_object_t *obj)
if (obj->trash_stack[UCL_TRASH_VALUE] != NULL) {
UCL_FREE (obj->len, obj->trash_stack[UCL_TRASH_VALUE]);
}
- UCL_FREE (sizeof (ucl_object_t), obj);
+ /* Do not free ephemeral objects */
+ if ((obj->flags & UCL_OBJECT_EPHEMERAL) == 0) {
+ if (obj->type != UCL_USERDATA) {
+ UCL_FREE (sizeof (ucl_object_t), obj);
+ }
+ else {
+ struct ucl_object_userdata *ud = (struct ucl_object_userdata *)obj;
+ if (ud->dtor) {
+ ud->dtor (obj->value.ud);
+ }
+ UCL_FREE (sizeof (*ud), obj);
+ }
+ }
}
/*
@@ -423,7 +432,11 @@ ucl_parser_free (struct ucl_parser *parser)
}
if (parser->err != NULL) {
- utstring_free(parser->err);
+ utstring_free (parser->err);
+ }
+
+ if (parser->cur_file) {
+ free (parser->cur_file);
}
UCL_FREE (sizeof (struct ucl_parser), parser);
@@ -701,7 +714,8 @@ ucl_sig_check (const unsigned char *data, size_t datalen,
*/
static bool
ucl_include_url (const unsigned char *data, size_t len,
- struct ucl_parser *parser, bool check_signature, bool must_exist)
+ struct ucl_parser *parser, bool check_signature, bool must_exist,
+ unsigned priority)
{
bool res;
@@ -744,7 +758,7 @@ ucl_include_url (const unsigned char *data, size_t len,
prev_state = parser->state;
parser->state = UCL_STATE_INIT;
- res = ucl_parser_add_chunk (parser, buf, buflen);
+ res = ucl_parser_add_chunk_priority (parser, buf, buflen, priority);
if (res == true) {
/* Remove chunk from the stack */
chunk = parser->chunks;
@@ -761,23 +775,30 @@ ucl_include_url (const unsigned char *data, size_t len,
}
/**
- * Include a file to configuration
+ * Include a single file to the parser
* @param data
* @param len
* @param parser
- * @param err
+ * @param check_signature
+ * @param must_exist
+ * @param allow_glob
+ * @param priority
* @return
*/
static bool
-ucl_include_file (const unsigned char *data, size_t len,
- struct ucl_parser *parser, bool check_signature, bool must_exist)
+ucl_include_file_single (const unsigned char *data, size_t len,
+ struct ucl_parser *parser, bool check_signature, bool must_exist,
+ unsigned priority)
{
bool res;
struct ucl_chunk *chunk;
unsigned char *buf = NULL;
+ char *old_curfile;
size_t buflen;
char filebuf[PATH_MAX], realbuf[PATH_MAX];
int prev_state;
+ struct ucl_variable *cur_var, *tmp_var, *old_curdir = NULL,
+ *old_filename = NULL;
snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data);
if (ucl_realpath (filebuf, realbuf) == NULL) {
@@ -790,6 +811,13 @@ ucl_include_file (const unsigned char *data, size_t len,
return false;
}
+ if (parser->cur_file && strcmp (realbuf, parser->cur_file) == 0) {
+ /* We are likely including the file itself */
+ ucl_create_err (&parser->err, "trying to include the file %s from itself",
+ realbuf);
+ return false;
+ }
+
if (!ucl_fetch_file (realbuf, &buf, &buflen, &parser->err, must_exist)) {
return (!must_exist || false);
}
@@ -818,19 +846,66 @@ ucl_include_file (const unsigned char *data, size_t len,
#endif
}
+ old_curfile = parser->cur_file;
+ parser->cur_file = strdup (realbuf);
+
+ /* Store old file vars */
+ DL_FOREACH_SAFE (parser->variables, cur_var, tmp_var) {
+ if (strcmp (cur_var->var, "CURDIR") == 0) {
+ old_curdir = cur_var;
+ DL_DELETE (parser->variables, cur_var);
+ }
+ else if (strcmp (cur_var->var, "FILENAME") == 0) {
+ old_filename = cur_var;
+ DL_DELETE (parser->variables, cur_var);
+ }
+ }
+
ucl_parser_set_filevars (parser, realbuf, false);
prev_state = parser->state;
parser->state = UCL_STATE_INIT;
- res = ucl_parser_add_chunk (parser, buf, buflen);
- if (res == true) {
- /* Remove chunk from the stack */
- chunk = parser->chunks;
- if (chunk != NULL) {
- parser->chunks = chunk->next;
- UCL_FREE (sizeof (struct ucl_chunk), chunk);
+ res = ucl_parser_add_chunk_priority (parser, buf, buflen, priority);
+ if (!res && !must_exist) {
+ /* Free error */
+ utstring_free (parser->err);
+ parser->err = NULL;
+ parser->state = UCL_STATE_AFTER_VALUE;
+ }
+
+ /* Remove chunk from the stack */
+ chunk = parser->chunks;
+ if (chunk != NULL) {
+ parser->chunks = chunk->next;
+ UCL_FREE (sizeof (struct ucl_chunk), chunk);
+ parser->recursion --;
+ }
+
+ /* Restore old file vars */
+ parser->cur_file = old_curfile;
+ DL_FOREACH_SAFE (parser->variables, cur_var, tmp_var) {
+ if (strcmp (cur_var->var, "CURDIR") == 0 && old_curdir) {
+ DL_DELETE (parser->variables, cur_var);
+ free (cur_var->var);
+ free (cur_var->value);
+ UCL_FREE (sizeof (struct ucl_variable), cur_var);
}
+ else if (strcmp (cur_var->var, "FILENAME") == 0 && old_filename) {
+ DL_DELETE (parser->variables, cur_var);
+ free (cur_var->var);
+ free (cur_var->value);
+ UCL_FREE (sizeof (struct ucl_variable), cur_var);
+ }
+ }
+ if (old_filename) {
+ DL_APPEND (parser->variables, old_filename);
+ }
+ if (old_curdir) {
+ DL_APPEND (parser->variables, old_curdir);
+ }
+ if (old_curfile) {
+ free (old_curfile);
}
parser->state = prev_state;
@@ -843,6 +918,138 @@ ucl_include_file (const unsigned char *data, size_t len,
}
/**
+ * Include a file to configuration
+ * @param data
+ * @param len
+ * @param parser
+ * @param err
+ * @return
+ */
+static bool
+ucl_include_file (const unsigned char *data, size_t len,
+ struct ucl_parser *parser, bool check_signature, bool must_exist,
+ bool allow_glob, unsigned priority)
+{
+ const unsigned char *p = data, *end = data + len;
+ bool need_glob = false;
+ int cnt = 0;
+ glob_t globbuf;
+ char glob_pattern[PATH_MAX];
+ size_t i;
+
+ if (!allow_glob) {
+ return ucl_include_file_single (data, len, parser, check_signature,
+ must_exist, priority);
+ }
+ else {
+ /* Check for special symbols in a filename */
+ while (p != end) {
+ if (*p == '*' || *p == '?') {
+ need_glob = true;
+ break;
+ }
+ p ++;
+ }
+ if (need_glob) {
+ memset (&globbuf, 0, sizeof (globbuf));
+ ucl_strlcpy (glob_pattern, (const char *)data, sizeof (glob_pattern));
+ if (glob (glob_pattern, 0, NULL, &globbuf) != 0) {
+ return (!must_exist || false);
+ }
+ for (i = 0; i < globbuf.gl_pathc; i ++) {
+ if (!ucl_include_file_single ((unsigned char *)globbuf.gl_pathv[i],
+ strlen (globbuf.gl_pathv[i]), parser, check_signature,
+ must_exist, priority)) {
+ globfree (&globbuf);
+ return false;
+ }
+ cnt ++;
+ }
+ globfree (&globbuf);
+
+ if (cnt == 0 && must_exist) {
+ ucl_create_err (&parser->err, "cannot match any files for pattern %s",
+ glob_pattern);
+ return false;
+ }
+ }
+ else {
+ return ucl_include_file_single (data, len, parser, check_signature,
+ must_exist, priority);
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Common function to handle .*include* macros
+ * @param data
+ * @param len
+ * @param args
+ * @param parser
+ * @param default_try
+ * @param default_sign
+ * @return
+ */
+static bool
+ucl_include_common (const unsigned char *data, size_t len,
+ const ucl_object_t *args, struct ucl_parser *parser,
+ bool default_try,
+ bool default_sign)
+{
+ bool try_load, allow_glob, allow_url, need_sign;
+ unsigned priority;
+ const ucl_object_t *param;
+ ucl_object_iter_t it = NULL;
+
+ /* Default values */
+ try_load = default_try;
+ allow_glob = false;
+ allow_url = true;
+ need_sign = default_sign;
+ priority = 0;
+
+ /* Process arguments */
+ if (args != NULL && args->type == UCL_OBJECT) {
+ while ((param = ucl_iterate_object (args, &it, true)) != NULL) {
+ if (param->type == UCL_BOOLEAN) {
+ if (strcmp (param->key, "try") == 0) {
+ try_load = ucl_object_toboolean (param);
+ }
+ else if (strcmp (param->key, "sign") == 0) {
+ need_sign = ucl_object_toboolean (param);
+ }
+ else if (strcmp (param->key, "glob") == 0) {
+ allow_glob = ucl_object_toboolean (param);
+ }
+ else if (strcmp (param->key, "url") == 0) {
+ allow_url = ucl_object_toboolean (param);
+ }
+ }
+ else if (param->type == UCL_INT) {
+ if (strcmp (param->key, "priority") == 0) {
+ priority = ucl_object_toint (param);
+ }
+ }
+ }
+ }
+
+ if (*data == '/' || *data == '.') {
+ /* Try to load a file */
+ return ucl_include_file (data, len, parser, need_sign, !try_load,
+ allow_glob, priority);
+ }
+ else if (allow_url) {
+ /* Globbing is not used for URL's */
+ return ucl_include_url (data, len, parser, need_sign, !try_load,
+ priority);
+ }
+
+ return false;
+}
+
+/**
* Handle include macro
* @param data include data
* @param len length of data
@@ -851,16 +1058,12 @@ ucl_include_file (const unsigned char *data, size_t len,
* @return
*/
UCL_EXTERN bool
-ucl_include_handler (const unsigned char *data, size_t len, void* ud)
+ucl_include_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud)
{
struct ucl_parser *parser = ud;
- if (*data == '/' || *data == '.') {
- /* Try to load a file */
- return ucl_include_file (data, len, parser, false, true);
- }
-
- return ucl_include_url (data, len, parser, false, true);
+ return ucl_include_common (data, len, args, parser, false, false);
}
/**
@@ -872,30 +1075,22 @@ ucl_include_handler (const unsigned char *data, size_t len, void* ud)
* @return
*/
UCL_EXTERN bool
-ucl_includes_handler (const unsigned char *data, size_t len, void* ud)
+ucl_includes_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud)
{
struct ucl_parser *parser = ud;
- if (*data == '/' || *data == '.') {
- /* Try to load a file */
- return ucl_include_file (data, len, parser, true, true);
- }
-
- return ucl_include_url (data, len, parser, true, true);
+ return ucl_include_common (data, len, args, parser, false, true);
}
UCL_EXTERN bool
-ucl_try_include_handler (const unsigned char *data, size_t len, void* ud)
+ucl_try_include_handler (const unsigned char *data, size_t len,
+ const ucl_object_t *args, void* ud)
{
struct ucl_parser *parser = ud;
- if (*data == '/' || *data == '.') {
- /* Try to load a file */
- return ucl_include_file (data, len, parser, false, false);
- }
-
- return ucl_include_url (data, len, parser, false, false);
+ return ucl_include_common (data, len, args, parser, true, false);
}
UCL_EXTERN bool
@@ -947,6 +1142,10 @@ ucl_parser_add_file (struct ucl_parser *parser, const char *filename)
return false;
}
+ if (parser->cur_file) {
+ free (parser->cur_file);
+ }
+ parser->cur_file = strdup (realbuf);
ucl_parser_set_filevars (parser, realbuf, false);
ret = ucl_parser_add_chunk (parser, buf, len);
@@ -957,6 +1156,39 @@ ucl_parser_add_file (struct ucl_parser *parser, const char *filename)
return ret;
}
+UCL_EXTERN bool
+ucl_parser_add_fd (struct ucl_parser *parser, int fd)
+{
+ unsigned char *buf;
+ size_t len;
+ bool ret;
+ struct stat st;
+
+ if (fstat (fd, &st) == -1) {
+ ucl_create_err (&parser->err, "cannot stat fd %d: %s",
+ fd, strerror (errno));
+ return false;
+ }
+ if ((buf = ucl_mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ ucl_create_err (&parser->err, "cannot mmap fd %d: %s",
+ fd, strerror (errno));
+ return false;
+ }
+
+ if (parser->cur_file) {
+ free (parser->cur_file);
+ }
+ parser->cur_file = NULL;
+ len = st.st_size;
+ ret = ucl_parser_add_chunk (parser, buf, len);
+
+ if (len > 0) {
+ ucl_munmap (buf, len);
+ }
+
+ return ret;
+}
+
size_t
ucl_strlcpy (char *dst, const char *src, size_t siz)
{
@@ -1176,6 +1408,15 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
}
}
+ /* workaround for some use cases */
+ if (elt->trash_stack[UCL_TRASH_KEY] != NULL &&
+ key != (const char *)elt->trash_stack[UCL_TRASH_KEY]) {
+ /* Remove copied key */
+ free (elt->trash_stack[UCL_TRASH_KEY]);
+ elt->trash_stack[UCL_TRASH_KEY] = NULL;
+ elt->flags &= ~UCL_OBJECT_ALLOCATED_KEY;
+ }
+
elt->key = key;
elt->keylen = keylen;
@@ -1185,9 +1426,8 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
found = __DECONST (ucl_object_t *, ucl_hash_search_obj (top->value.ov, elt));
- if (!found) {
+ if (found == NULL) {
top->value.ov = ucl_hash_insert_object (top->value.ov, elt);
- DL_APPEND (found, elt);
top->len ++;
if (replace) {
ret = false;
@@ -1195,11 +1435,8 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
}
else {
if (replace) {
- ucl_hash_delete (top->value.ov, found);
+ ucl_hash_replace (top->value.ov, found, elt);
ucl_object_unref (found);
- top->value.ov = ucl_hash_insert_object (top->value.ov, elt);
- found = NULL;
- DL_APPEND (found, elt);
}
else if (merge) {
if (found->type != UCL_OBJECT && elt->type == UCL_OBJECT) {
@@ -1310,6 +1547,40 @@ ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt,
return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, false, true);
}
+bool
+ucl_object_merge (ucl_object_t *top, ucl_object_t *elt, bool copy)
+{
+ ucl_object_t *cur = NULL, *cp = NULL, *found = NULL;
+ ucl_object_iter_t iter = NULL;
+
+ if (top == NULL || top->type != UCL_OBJECT || elt == NULL || elt->type != UCL_OBJECT) {
+ return false;
+ }
+
+ /* Mix two hashes */
+ while ((cur = (ucl_object_t*)ucl_hash_iterate (elt->value.ov, &iter))) {
+ if (copy) {
+ cp = ucl_object_copy (cur);
+ }
+ else {
+ cp = ucl_object_ref (cur);
+ }
+ found = __DECONST(ucl_object_t *, ucl_hash_search (top->value.ov, cp->key, cp->keylen));
+ if (found == NULL) {
+ /* The key does not exist */
+ top->value.ov = ucl_hash_insert_object (top->value.ov, cp);
+ top->len ++;
+ }
+ else {
+ /* The key already exists, replace it */
+ ucl_hash_replace (top->value.ov, found, cp);
+ ucl_object_unref (found);
+ }
+ }
+
+ return true;
+}
+
const ucl_object_t *
ucl_object_find_keyl (const ucl_object_t *obj, const char *key, size_t klen)
{
@@ -1372,9 +1643,6 @@ ucl_iterate_object (const ucl_object_t *obj, ucl_object_iter_t *iter, bool expan
elt = *iter;
if (elt == NULL) {
elt = obj;
- if (elt == NULL) {
- return NULL;
- }
}
else if (elt == obj) {
return NULL;
@@ -1442,29 +1710,59 @@ ucl_lookup_path (const ucl_object_t *top, const char *path_in) {
ucl_object_t *
ucl_object_new (void)
{
- ucl_object_t *new;
- new = malloc (sizeof (ucl_object_t));
- if (new != NULL) {
- memset (new, 0, sizeof (ucl_object_t));
- new->ref = 1;
- new->type = UCL_NULL;
- }
- return new;
+ return ucl_object_typed_new (UCL_NULL);
}
ucl_object_t *
ucl_object_typed_new (ucl_type_t type)
{
+ return ucl_object_new_full (type, 0);
+}
+
+ucl_object_t *
+ucl_object_new_full (ucl_type_t type, unsigned priority)
+{
ucl_object_t *new;
- new = malloc (sizeof (ucl_object_t));
- if (new != NULL) {
- memset (new, 0, sizeof (ucl_object_t));
- new->ref = 1;
- new->type = (type <= UCL_NULL ? type : UCL_NULL);
+
+ if (type != UCL_USERDATA) {
+ new = UCL_ALLOC (sizeof (ucl_object_t));
+ if (new != NULL) {
+ memset (new, 0, sizeof (ucl_object_t));
+ new->ref = 1;
+ new->type = (type <= UCL_NULL ? type : UCL_NULL);
+ new->next = NULL;
+ new->prev = new;
+ ucl_object_set_priority (new, priority);
+ }
+ }
+ else {
+ new = ucl_object_new_userdata (NULL, NULL);
+ ucl_object_set_priority (new, priority);
}
+
return new;
}
+ucl_object_t*
+ucl_object_new_userdata (ucl_userdata_dtor dtor, ucl_userdata_emitter emitter)
+{
+ struct ucl_object_userdata *new;
+ size_t nsize = sizeof (*new);
+
+ new = UCL_ALLOC (nsize);
+ if (new != NULL) {
+ memset (new, 0, nsize);
+ new->obj.ref = 1;
+ new->obj.type = UCL_USERDATA;
+ new->obj.next = NULL;
+ new->obj.prev = (ucl_object_t *)new;
+ new->dtor = dtor;
+ new->emitter = emitter;
+ }
+
+ return (ucl_object_t *)new;
+}
+
ucl_type_t
ucl_object_type (const ucl_object_t *obj)
{
@@ -1576,6 +1874,30 @@ ucl_array_prepend (ucl_object_t *top, ucl_object_t *elt)
return true;
}
+bool
+ucl_array_merge (ucl_object_t *top, ucl_object_t *elt, bool copy)
+{
+ ucl_object_t *cur, *tmp, *cp;
+
+ if (elt == NULL || top == NULL || top->type != UCL_ARRAY || elt->type != UCL_ARRAY) {
+ return false;
+ }
+
+ DL_FOREACH_SAFE (elt->value.av, cur, tmp) {
+ if (copy) {
+ cp = ucl_object_copy (cur);
+ }
+ else {
+ cp = ucl_object_ref (cur);
+ }
+ if (cp != NULL) {
+ ucl_array_append (top, cp);
+ }
+ }
+
+ return true;
+}
+
ucl_object_t *
ucl_array_delete (ucl_object_t *top, ucl_object_t *elt)
{
@@ -1661,6 +1983,28 @@ ucl_array_find_index (const ucl_object_t *top, unsigned int index)
}
ucl_object_t *
+ucl_array_replace_index (ucl_object_t *top, ucl_object_t *elt,
+ unsigned int index)
+{
+ ucl_object_t *cur, *tmp;
+
+ if (top == NULL || top->type != UCL_ARRAY || elt == NULL ||
+ top->len == 0 || (index + 1) > top->len) {
+ return NULL;
+ }
+
+ DL_FOREACH_SAFE (top->value.av, cur, tmp) {
+ if (index == 0) {
+ DL_REPLACE_ELEM (top->value.av, cur, elt);
+ return cur;
+ }
+ --index;
+ }
+
+ return NULL;
+}
+
+ucl_object_t *
ucl_elt_append (ucl_object_t *head, ucl_object_t *elt)
{
@@ -1849,16 +2193,99 @@ ucl_object_ref (const ucl_object_t *obj)
ucl_object_t *res = NULL;
if (obj != NULL) {
- res = __DECONST (ucl_object_t *, obj);
+ if (obj->flags & UCL_OBJECT_EPHEMERAL) {
+ /*
+ * Use deep copy for ephemeral objects, note that its refcount
+ * is NOT increased, since ephemeral objects does not need refcount
+ * at all
+ */
+ res = ucl_object_copy (obj);
+ }
+ else {
+ res = __DECONST (ucl_object_t *, obj);
#ifdef HAVE_ATOMIC_BUILTINS
- (void)__sync_add_and_fetch (&res->ref, 1);
+ (void)__sync_add_and_fetch (&res->ref, 1);
#else
- res->ref ++;
+ res->ref ++;
#endif
+ }
}
return res;
}
+static ucl_object_t *
+ucl_object_copy_internal (const ucl_object_t *other, bool allow_array)
+{
+
+ ucl_object_t *new;
+ ucl_object_iter_t it = NULL;
+ const ucl_object_t *cur;
+
+ new = malloc (sizeof (*new));
+
+ if (new != NULL) {
+ memcpy (new, other, sizeof (*new));
+ if (other->flags & UCL_OBJECT_EPHEMERAL) {
+ /* Copied object is always non ephemeral */
+ new->flags &= ~UCL_OBJECT_EPHEMERAL;
+ }
+ new->ref = 1;
+ /* Unlink from others */
+ new->next = NULL;
+ new->prev = new;
+
+ /* deep copy of values stored */
+ if (other->trash_stack[UCL_TRASH_KEY] != NULL) {
+ new->trash_stack[UCL_TRASH_KEY] =
+ strdup (other->trash_stack[UCL_TRASH_KEY]);
+ if (other->key == (const char *)other->trash_stack[UCL_TRASH_KEY]) {
+ new->key = new->trash_stack[UCL_TRASH_KEY];
+ }
+ }
+ if (other->trash_stack[UCL_TRASH_VALUE] != NULL) {
+ new->trash_stack[UCL_TRASH_VALUE] =
+ strdup (other->trash_stack[UCL_TRASH_VALUE]);
+ if (new->type == UCL_STRING) {
+ new->value.sv = new->trash_stack[UCL_TRASH_VALUE];
+ }
+ }
+
+ if (other->type == UCL_ARRAY || other->type == UCL_OBJECT) {
+ /* reset old value */
+ memset (&new->value, 0, sizeof (new->value));
+
+ while ((cur = ucl_iterate_object (other, &it, true)) != NULL) {
+ if (other->type == UCL_ARRAY) {
+ ucl_array_append (new, ucl_object_copy_internal (cur, false));
+ }
+ else {
+ ucl_object_t *cp = ucl_object_copy_internal (cur, true);
+ if (cp != NULL) {
+ ucl_object_insert_key (new, cp, cp->key, cp->keylen,
+ false);
+ }
+ }
+ }
+ }
+ else if (allow_array && other->next != NULL) {
+ LL_FOREACH (other->next, cur) {
+ ucl_object_t *cp = ucl_object_copy_internal (cur, false);
+ if (cp != NULL) {
+ DL_APPEND (new, cp);
+ }
+ }
+ }
+ }
+
+ return new;
+}
+
+ucl_object_t *
+ucl_object_copy (const ucl_object_t *other)
+{
+ return ucl_object_copy_internal (other, true);
+}
+
void
ucl_object_unref (ucl_object_t *obj)
{
@@ -1956,3 +2383,25 @@ ucl_object_array_sort (ucl_object_t *ar,
DL_SORT (ar->value.av, cmp);
}
+
+#define PRIOBITS 4
+
+unsigned int
+ucl_object_get_priority (const ucl_object_t *obj)
+{
+ if (obj == NULL) {
+ return 0;
+ }
+
+ return (obj->flags >> ((sizeof (obj->flags) * NBBY) - PRIOBITS));
+}
+
+void
+ucl_object_set_priority (ucl_object_t *obj,
+ unsigned int priority)
+{
+ if (obj != NULL) {
+ priority &= (0x1 << PRIOBITS) - 1;
+ obj->flags |= priority << ((sizeof (obj->flags) * NBBY) - PRIOBITS);
+ }
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a21a65c16534..5b17e1fb252e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,4 +1,4 @@
-EXTRA_DIST = $(TESTS) basic schema generate.res rcl_test.json.xz
+EXTRA_DIST = $(TESTS) basic schema generate.res streamline.res rcl_test.json.xz
TESTS = basic.test \
generate.test \
diff --git a/tests/basic/12.in b/tests/basic/12.in
new file mode 100644
index 000000000000..b63a32fbf92d
--- /dev/null
+++ b/tests/basic/12.in
@@ -0,0 +1,2 @@
+key1: 12 ,
+key2: 12 value
diff --git a/tests/basic/12.res b/tests/basic/12.res
new file mode 100644
index 000000000000..163af46c7f17
--- /dev/null
+++ b/tests/basic/12.res
@@ -0,0 +1,3 @@
+key1 = 12;
+key2 = "12 value";
+
diff --git a/tests/basic/13.in b/tests/basic/13.in
new file mode 100644
index 000000000000..81f03a61d286
--- /dev/null
+++ b/tests/basic/13.in
@@ -0,0 +1,9 @@
+key = value_orig;
+
+# test glob
+.include(glob=true) "${CURDIR}/include_dir/test*.conf"
+
+.include(priority=1) "${CURDIR}/include_dir/pri1.conf"
+.include(priority=2) "${CURDIR}/include_dir/pri2.conf"
+
+.include(try=true) "${CURDIR}/include_dir/invalid.conf"
diff --git a/tests/basic/13.res b/tests/basic/13.res
new file mode 100644
index 000000000000..aa13a5471f2e
--- /dev/null
+++ b/tests/basic/13.res
@@ -0,0 +1,8 @@
+key = "value_orig";
+key = "value1";
+key = "value2";
+key = "value3";
+key_pri = "priority2";
+key_trace1 = "pri1";
+key_trace2 = "pri2";
+
diff --git a/tests/basic/4.res b/tests/basic/4.res
index 58c3599432b8..2b862fc1410a 100644
--- a/tests/basic/4.res
+++ b/tests/basic/4.res
@@ -10,7 +10,14 @@ 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";
+desc = <<EOD
+pkgconf is a program which helps to configure compiler and linker flags for
+development frameworks. It is similar to pkg-config, but was written from
+scratch in Summer of 2011 to replace pkg-config, which now needs itself to build
+itself.
+
+WWW: https://github.com/pkgconf/pkgconf
+EOD;
categories [
"devel",
]
@@ -31,6 +38,17 @@ scripts {
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";
+multiline-key = <<EOD
+test
+test
+test\n
+/* comment like */
+# Some invalid endings
+ EOD
+EOD
+EOF
+# Valid ending + empty string
+
+EOD;
normal-key = "<<EODnot";
diff --git a/tests/basic/comments.in b/tests/basic/comments.in
new file mode 100644
index 000000000000..3144fa004fa2
--- /dev/null
+++ b/tests/basic/comments.in
@@ -0,0 +1,25 @@
+# This test is intended to check various comments in ucl
+
+obj {
+
+ key = value
+ key = "/* value"
+ /*
+ key = value
+ */
+# Nested comments
+ key = nested
+ /*
+ adasdasdads
+ /* asdasdasd */asjdasjldaskd
+ /* asdsadasd */
+ /* /* /* /* /* */ */ */ */ */
+# some
+ */
+ key = quotes # quoted
+# Quotes
+ /*
+ key = "/* value"
+ key = "*/value"
+ */
+}
diff --git a/tests/basic/comments.res b/tests/basic/comments.res
new file mode 100644
index 000000000000..4c3a79714170
--- /dev/null
+++ b/tests/basic/comments.res
@@ -0,0 +1,7 @@
+obj {
+ key = "value";
+ key = "/* value";
+ key = "nested";
+ key = "quotes";
+}
+
diff --git a/tests/basic/include_dir/invalid.conf b/tests/basic/include_dir/invalid.conf
new file mode 100644
index 000000000000..c3267cb244e9
--- /dev/null
+++ b/tests/basic/include_dir/invalid.conf
@@ -0,0 +1 @@
+@@@@ BAD UCL ~~~~
diff --git a/tests/basic/include_dir/pri1.conf b/tests/basic/include_dir/pri1.conf
new file mode 100644
index 000000000000..d21843367319
--- /dev/null
+++ b/tests/basic/include_dir/pri1.conf
@@ -0,0 +1,2 @@
+key_pri = priority1;
+key_trace1 = pri1;
diff --git a/tests/basic/include_dir/pri2.conf b/tests/basic/include_dir/pri2.conf
new file mode 100644
index 000000000000..ee734a82836f
--- /dev/null
+++ b/tests/basic/include_dir/pri2.conf
@@ -0,0 +1,2 @@
+key_pri = priority2;
+key_trace2 = pri2;
diff --git a/tests/basic/include_dir/test1.conf b/tests/basic/include_dir/test1.conf
new file mode 100644
index 000000000000..61f290659a2f
--- /dev/null
+++ b/tests/basic/include_dir/test1.conf
@@ -0,0 +1 @@
+key = value1;
diff --git a/tests/basic/include_dir/test2.conf b/tests/basic/include_dir/test2.conf
new file mode 100644
index 000000000000..0b5542792376
--- /dev/null
+++ b/tests/basic/include_dir/test2.conf
@@ -0,0 +1 @@
+key = value2;
diff --git a/tests/basic/include_dir/test3.conf b/tests/basic/include_dir/test3.conf
new file mode 100644
index 000000000000..3ceb7dd492d2
--- /dev/null
+++ b/tests/basic/include_dir/test3.conf
@@ -0,0 +1 @@
+key = value3;
diff --git a/tests/generate.res b/tests/generate.res
index 1d39478e0239..970b3125bae6 100644
--- a/tests/generate.res
+++ b/tests/generate.res
@@ -1,3 +1,4 @@
+key0 = 0.100000;
key1 = "test string";
key2 = "test \\nstring";
key3 = " test string \n";
diff --git a/tests/test_basic.c b/tests/test_basic.c
index b4ed7788dce4..5859c0ba6eb7 100644
--- a/tests/test_basic.c
+++ b/tests/test_basic.c
@@ -90,7 +90,7 @@ main (int argc, char **argv)
inlen = strlen (inbuf);
test_in = malloc (inlen);
memcpy (test_in, inbuf, inlen);
- ucl_parser_add_chunk (parser, test_in, inlen);
+ ucl_parser_add_chunk (parser, (const unsigned char *)test_in, inlen);
}
fclose (in);
@@ -126,7 +126,7 @@ main (int argc, char **argv)
ucl_parser_free (parser);
ucl_object_unref (obj);
parser2 = ucl_parser_new (UCL_PARSER_KEY_LOWERCASE);
- ucl_parser_add_string (parser2, emitted, 0);
+ ucl_parser_add_string (parser2, (const char *)emitted, 0);
if (ucl_parser_get_error(parser2) != NULL) {
fprintf (out, "Error occurred: %s\n", ucl_parser_get_error(parser2));
diff --git a/tests/test_generate.c b/tests/test_generate.c
index 5c130e67499a..aad92b51fb0e 100644
--- a/tests/test_generate.c
+++ b/tests/test_generate.c
@@ -54,6 +54,13 @@ main (int argc, char **argv)
}
obj = ucl_object_typed_new (UCL_OBJECT);
+
+ /* Keys replacing */
+ cur = ucl_object_fromstring_common ("value1", 0, UCL_STRING_TRIM);
+ ucl_object_insert_key (obj, cur, "key0", 0, false);
+ cur = ucl_object_fromdouble (0.1);
+ ucl_object_replace_key (obj, cur, "key0", 0, false);
+
/* Create some strings */
cur = ucl_object_fromstring_common (" test string ", 0, UCL_STRING_TRIM);
ucl_object_insert_key (obj, cur, "key1", 0, false);
diff --git a/tests/test_schema.c b/tests/test_schema.c
index afce17816d50..7acebb84b934 100644
--- a/tests/test_schema.c
+++ b/tests/test_schema.c
@@ -61,7 +61,7 @@ static bool
perform_test (const ucl_object_t *schema, const ucl_object_t *obj,
struct ucl_schema_error *err)
{
- const const ucl_object_t *valid, *data, *description;
+ const ucl_object_t *valid, *data, *description;
bool match;
data = ucl_object_find_key (obj, "data");
diff --git a/utils/objdump.c b/utils/objdump.c
index 2e00d0abc2f3..d3f7cd1bcec6 100644
--- a/utils/objdump.c
+++ b/utils/objdump.c
@@ -53,6 +53,7 @@ ucl_obj_dump (const ucl_object_t *obj, unsigned int shift)
if (obj->type == UCL_OBJECT) {
printf ("%stype: UCL_OBJECT\n", pre);
printf ("%svalue: %p\n", pre, obj->value.ov);
+ it_obj = NULL;
while ((cur = ucl_iterate_object (obj, &it_obj, true))) {
ucl_obj_dump (cur, shift + 2);
}