changeset 1:2df0a7f85837 default tip

today's batch
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Wed, 08 Jan 2020 15:37:43 -0500
parents 3e042207d646
children
files 2020-01-08-1-WIP__nvlist__unpack_framework.patch 2020-01-08-2-WIP__nvlist__subtype_support.patch 2020-01-08-3-WIP__nvlist__sexpr_packing.patch
diffstat 3 files changed, 815 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2020-01-08-1-WIP__nvlist__unpack_framework.patch	Wed Jan 08 15:37:43 2020 -0500
@@ -0,0 +1,647 @@
+# HG changeset patch
+# User Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Date 1502202289 -10800
+#      Tue Aug 08 17:24:49 2017 +0300
+# Node ID 09eb09529f368dd34076f81cc4bfb3f61f47299c
+# Parent  4241b66d58237305a29789dc7657cad5eb8c5d19
+# EXP-Topic nvlist-unpack-framework
+WIP: nvlist: unpack framework
+
+Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+
+diff --git a/nvl_fmt_cbor.c b/nvl_fmt_cbor.c
+--- a/nvl_fmt_cbor.c
++++ b/nvl_fmt_cbor.c
+@@ -51,6 +51,389 @@ static int cbor_array_epilogue(struct bu
+ 	return cbor_pack_array_end(buffer, nelem);
+ }
+ 
++/*
++ * unpack
++ */
++
++static int parse_addl(struct buffer *buffer, uint64_t *addl)
++{
++	const uint8_t *bytes;
++	uint8_t additional;
++	ssize_t ret;
++	size_t len;
++	size_t i;
++
++	bytes = buffer_data_current(buffer);
++
++	additional = bytes[0] & 0x1f;
++
++	if (additional <= 23) {
++		len = 0;
++		*addl = additional;
++	} else if (additional == ADDL_UINT8) {
++		len = 1;
++		*addl = 0;
++	} else if (additional == ADDL_UINT16) {
++		len = 2;
++		*addl = 0;
++	} else if (additional == ADDL_UINT32) {
++		len = 4;
++		*addl = 0;
++	} else if (additional == ADDL_UINT64) {
++		len = 8;
++		*addl = 0;
++	} else {
++		return -EILSEQ;
++	}
++
++	if (buffer_remain(buffer) < len)
++		return -ESRCH;
++
++	for (i = 0; i < len; i++) {
++		*addl <<= 8;
++		*addl |= bytes[i + 1];
++	}
++
++	ret = buffer_seek(buffer, len + 1, SEEK_CUR);
++
++	return (ret < 0) ? ret : 0;
++}
++
++static int cbor_parse_nvl(struct buffer *buffer, uint8_t byte,
++			  struct nvval *val);
++static int cbor_parse_val(struct buffer *buffer, struct nvval *val);
++
++static int cbor_parse_bool(struct buffer *buffer, uint8_t byte,
++			   struct nvval *val)
++{
++	ssize_t ret;
++
++	val->type = NVT_BOOL;
++	val->b = (byte & 0x1f) == ADDL_FLOAT_TRUE;
++
++	ret = buffer_seek(buffer, 1, SEEK_CUR);
++
++	return (ret < 0) ? ret : 0;
++}
++
++static int cbor_parse_null(struct buffer *buffer, struct nvval *val)
++{
++	ssize_t ret;
++
++	val->type = NVT_NULL;
++
++	ret = buffer_seek(buffer, 1, SEEK_CUR);
++
++	return (ret < 0) ? ret : 0;
++}
++
++static int cbor_parse_uint(struct buffer *buffer, struct nvval *val)
++{
++	val->type = NVT_INT;
++
++	return parse_addl(buffer, &val->i);
++}
++
++static int cbor_parse_length(struct buffer *buffer, size_t *len)
++{
++	uint64_t tmp;
++	int ret;
++
++	ret = parse_addl(buffer, &tmp);
++	if (ret)
++		return ret;
++
++	if (tmp >= SIZE_MAX)
++		return -E2BIG;
++
++	*len = tmp;
++
++	ret = buffer_seek(buffer, 1, SEEK_CUR);
++
++	return (ret < 0) ? ret : 0;
++}
++
++static int inline get_byte_string(struct buffer *buffer, bool nulterm,
++				  char **buf_r, size_t *len_r)
++{
++	ssize_t ret;
++	size_t len;
++	char *tmp;
++
++	ret = cbor_parse_length(buffer, &len);
++	if (ret)
++		return ret;
++
++	tmp = malloc(len + (nulterm ? 1 : 0));
++	if (!tmp)
++		return -ENOMEM;
++
++	ret = buffer_read(buffer, tmp, len);
++	if (ret < 0)
++		return ret;
++
++	if (ret != len)
++		return -ESRCH;
++
++	if (nulterm)
++		tmp[len] = '\0';
++
++	*buf_r = tmp;
++	*len_r = len;
++
++	return 0;
++}
++
++static int cbor_parse_byte(struct buffer *buffer, struct nvval *val)
++{
++	size_t len;
++	char *tmp;
++	int ret;
++
++	ret = get_byte_string(buffer, false, &tmp, &len);
++	if (ret)
++		return ret;
++
++	val->type = NVT_BLOB;
++	val->blob.ptr = tmp;
++	val->blob.size = len;
++
++	return 0;
++}
++
++static int cbor_parse_text(struct buffer *buffer, struct nvval *val)
++{
++	struct str *str;
++	size_t len;
++	char *tmp;
++	int ret;
++
++	ret = get_byte_string(buffer, true, &tmp, &len);
++	if (ret)
++		return ret;
++
++	str = str_alloc(tmp);
++	if (!str)
++		return -ENOMEM;
++
++	val->type = NVT_STR;
++	val->str = str;
++
++	return 0;
++}
++
++static int cbor_parse_array_indef(struct buffer *buffer, struct nvval *val)
++{
++	struct nvval *vals;
++	size_t nvals;
++	ssize_t ret;
++
++	/* seek past the 1 byte with major type */
++	ret = buffer_seek(buffer, 1, SEEK_CUR);
++	if (ret)
++		return ret;
++
++	vals = NULL;
++	nvals = 0;
++
++	for (;;) {
++		const uint8_t *cur;
++		struct nvval *tmp;
++
++		cur = buffer_data_current(buffer);
++		if (!cur) {
++			ret = -ESRCH;
++			break;
++		}
++
++		if (*cur == MKTYPE_STATIC(CMT_FLOAT, ADDL_FLOAT_BREAK))
++			goto done;
++
++		tmp = reallocarray(vals, nvals + 1, sizeof(struct nvval));
++		if (!tmp) {
++			ret = -ENOMEM;
++			break;
++		}
++
++		vals = tmp;
++		nvals++;
++
++		ret = cbor_parse_val(buffer, &vals[nvals - 1]);
++		if (ret)
++			goto done;
++	}
++
++	nvval_release_array(vals, nvals);
++
++	free(vals);
++
++	return ret;
++
++done:
++	val->type = NVT_ARRAY;
++	val->array.vals = vals;
++	val->array.nelem = nvals;
++
++	return 0;
++}
++
++static int cbor_parse_array(struct buffer *buffer, uint8_t byte,
++			    struct nvval *val)
++{
++	if (byte == MKTYPE_STATIC(CMT_ARRAY, ADDL_ARRAY_INDEF))
++		return cbor_parse_array_indef(buffer, val);
++
++	/* definite length map */
++	return -ENOTSUP; /* FIXME */
++}
++
++static struct str *cbor_parse_str(struct buffer *buffer)
++{
++	struct nvval val;
++	int ret;
++
++	ret = cbor_parse_text(buffer, &val);
++	if (ret)
++		return ERR_PTR(ret);
++
++	if (val.type != NVT_STR) {
++		nvval_release(&val);
++		return ERR_PTR(-ENOTSUP);
++	}
++
++	return val.str;
++}
++
++static int cbor_parse_val(struct buffer *buffer, struct nvval *val)
++{
++	const uint8_t *cur;
++	uint8_t byte;
++
++	cur = buffer_data_current(buffer);
++	if (!cur)
++		return -ESRCH;
++
++	byte = *cur;
++
++	switch ((enum major_type) (byte >> 5)) {
++		case CMT_UINT:
++			return cbor_parse_uint(buffer, val);
++		case CMT_NINT:
++			return -ENOTSUP;
++		case CMT_BYTE:
++			return cbor_parse_byte(buffer, val);
++		case CMT_TEXT:
++			return cbor_parse_text(buffer, val);
++		case CMT_ARRAY:
++			return cbor_parse_array(buffer, byte, val);
++		case CMT_MAP:
++			return cbor_parse_nvl(buffer, byte, val);
++		case CMT_TAG:
++			return -ENOTSUP;
++		case CMT_FLOAT: {
++			switch (byte & 0x1f) {
++				case ADDL_FLOAT_FALSE:
++				case ADDL_FLOAT_TRUE:
++					return cbor_parse_bool(buffer, byte,
++							       val);
++				case ADDL_FLOAT_NULL:
++					return cbor_parse_null(buffer, val);
++				case ADDL_FLOAT_BREAK:
++					return -EILSEQ;
++			}
++
++			return -EILSEQ;
++		}
++	}
++
++	panic("%s encountered an impossible major type: %u", __func__,
++	      *cur >> 5);
++}
++
++static int cbor_parse_nvl_indef(struct buffer *buffer, struct nvlist *nvl)
++{
++	ssize_t ret;
++
++	/* seek past the 1 byte with major type */
++	ret = buffer_seek(buffer, 1, SEEK_CUR);
++	if (ret)
++		return ret;
++
++	for (;;) {
++		const uint8_t *cur;
++		struct nvval val;
++		struct str *name;
++
++		cur = buffer_data_current(buffer);
++		if (!cur)
++			return -ESRCH;
++
++		if (*cur == MKTYPE_STATIC(CMT_FLOAT, ADDL_FLOAT_BREAK))
++			return 0;
++
++		/* only string keys are supported */
++		if ((*cur >> 5) != CMT_TEXT)
++			return -ENOTSUP;
++
++		/* read the key */
++		name = cbor_parse_str(buffer);
++		if (IS_ERR(name))
++			return PTR_ERR(name);
++
++		/* read the value */
++		ret = cbor_parse_val(buffer, &val);
++		if (ret) {
++			str_putref(name);
++			return ret;
++		}
++
++		/* set the <key,value> pair */
++		ret = nvl_set(nvl, str_cstr(name), &val);
++		if (ret)
++			return ret;
++
++		str_putref(name);
++	}
++}
++
++static int cbor_parse_nvl(struct buffer *buffer, uint8_t byte,
++			  struct nvval *val)
++{
++	val->type = NVT_NVL;
++	val->nvl = nvl_alloc();
++	if (!val->nvl)
++		return -ENOMEM;
++
++	if (byte == MKTYPE_STATIC(CMT_MAP, ADDL_MAP_INDEF))
++		return cbor_parse_nvl_indef(buffer, val->nvl);
++
++	/* definite length nvlist */
++	nvl_putref(val->nvl);
++	return -ENOTSUP; /* FIXME */
++}
++
++static struct nvlist *cbor_parse(struct buffer *buffer)
++{
++	const uint8_t *cur;
++	struct nvval val;
++	int ret;
++
++	cur = buffer_data_current(buffer);
++	if (!cur)
++		return ERR_PTR(-ESRCH);
++
++	if ((*cur >> 5) != CMT_MAP)
++		return ERR_PTR(-ENOTSUP);
++
++	ret = cbor_parse_nvl(buffer, *cur, &val);
++	if (ret)
++		return ERR_PTR(ret);
++
++	ASSERT3U(val.type, ==, NVT_NVL);
++
++	return val.nvl;
++}
++
+ const struct nvops nvops_cbor = {
+ 	.pack = {
+ 		.nvl_prologue = cbor_nvl_prologue,
+@@ -65,4 +448,7 @@ const struct nvops nvops_cbor = {
+ 		.val_null = cbor_pack_null,
+ 		.val_str = cbor_pack_cstr,
+ 	}
++	.unpack = {
++		.parse = cbor_parse,
++	},
+ };
+diff --git a/nvl_fmt_json.c b/nvl_fmt_json.c
+--- a/nvl_fmt_json.c
++++ b/nvl_fmt_json.c
+@@ -24,6 +24,10 @@
+ 
+ #include "nvl_impl.h"
+ 
++/*
++ * pack
++ */
++
+ static int json_nvl_prologue(struct buffer *buffer, struct nvlist *nvl)
+ {
+ 	return buffer_append_c(buffer, '{');
+@@ -155,6 +159,123 @@ static int json_val_str(struct buffer *b
+ 	return 0;
+ }
+ 
++/*
++ * unpack
++ */
++
++static const char *consume_whitespace(struct buffer *buffer)
++{
++	const char *cur;
++	int ret;
++
++	while ((cur = buffer_data_current(buffer))) {
++		switch (*cur) {
++			case ' ':
++			case '\t':
++			case '\n':
++			case '\r':
++				break;
++			default:
++				return cur;
++		}
++
++		ret = buffer_seek(buffer, 1, SEEK_CUR);
++		if (ret)
++			return ERR_PTR(ret);
++	}
++
++	return NULL;
++}
++
++static inline bool sanity_check(struct buffer *buffer, const char *expected)
++{
++	const char *cur;
++	size_t rem;
++
++	cur = buffer_data_current(buffer);
++	if (!cur)
++		return false;
++
++	rem = buffer_remain(buffer);
++
++	rem = MIN(rem, strlen(expected));
++
++	return strncmp(cur, expected, rem) == 0;
++}
++
++static int json_parse_type(struct buffer *buffer, enum nvtype *type,
++			   uint64_t *addl)
++{
++	const char *cur;
++
++	cur = consume_whitespace(buffer);
++	if (!cur)
++		return -ESRCH;
++	if (IS_ERR(cur))
++		return PTR_ERR(cur);
++
++	*addl = ~0ul;
++
++	switch (*cur) {
++		case '{':
++			*type = NVT_NVL;
++			break;
++		case '[':
++			*type = NVT_ARRAY;
++			break;
++		case '"':
++			*type = NVT_STR;
++			break;
++		case '0': case '1': case '2': case '3': case '4':
++		case '5': case '6': case '7': case '8': case '9':
++			*type = NVT_INT;
++			*addl = *cur - '0';
++			break;
++		case 'n':
++			if (sanity_check(buffer, "null"))
++				*type = NVT_NULL;
++			else
++				return -EILSEQ;
++			break;
++		case 't':
++			if (sanity_check(buffer, "true")) {
++				*type = NVT_BOOL;
++				*addl = 1;
++			} else {
++				return -EILSEQ;
++			}
++			break;
++		case 'f':
++			if (sanity_check(buffer, "false")) {
++				*type = NVT_BOOL;
++				*addl = 0;
++			} else {
++				return -EILSEQ;
++			}
++			break;
++		default:
++			return -EILSEQ;
++	}
++
++	return 0;
++}
++
++static int json_parse_bool(struct buffer *buffer, uint64_t addl, bool *b)
++{
++	/* we already did all the parsing in the peek-type function */
++
++	*b = !!addl;
++
++	return 0;
++}
++
++static int json_parse_int(struct buffer *buffer, uint64_t addl, uint64_t *i)
++{
++	*i = addl;
++
++	return -ENOTSUP;
++}
++
+ const struct nvops nvops_json = {
+ 	.pack = {
+ 		.nvl_prologue = json_nvl_prologue,	/* { */
+@@ -170,5 +291,11 @@ const struct nvops nvops_json = {
+ 		.val_int = json_val_int,
+ 		.val_null = json_val_null,
+ 		.val_str = json_val_str,
+-	}
++	},
++	.unpack = {
++		.parse_type = json_parse_type,
++
++		.parse_bool = json_parse_bool,
++		.parse_int = json_parse_int,
++	},
+ };
+diff --git a/nvl_impl.h b/nvl_impl.h
+--- a/nvl_impl.h
++++ b/nvl_impl.h
+@@ -46,6 +46,21 @@
+ 		_ret;							\
+ 	 })
+ 
++struct unpack_tok {
++	enum unpack_tok_type {
++		UTT_VAL,	/* string, int, bool, null */
++		UTT_ARR_START,	/* start of array */
++		UTT_NVL_START,	/* start of nvl */
++		UTT_BREAK,	/* end of array or nvl */
++	} type;
++
++	/* UTT_VAL */
++	struct nvval val;
++
++	/* UTT_{NVL,ARR}_START */
++	size_t nelem;
++};
++
+ /*
+  * Packing operations
+  *
+@@ -107,7 +122,13 @@ struct nvpackops {
+ };
+ 
+ struct nvunpackops {
+-	/* TODO */
++	/*
++	 * Returns:
++	 *   nvlist on success
++	 *   -ESRCH if end of buffer
++	 *   -EILSEQ if malformed input
++	 */
++	struct nvlist *(*parse)(struct buffer *buffer);
+ };
+ 
+ struct nvops {
+diff --git a/nvl_unpack.c b/nvl_unpack.c
+--- a/nvl_unpack.c
++++ b/nvl_unpack.c
+@@ -25,35 +25,16 @@
+ 
+ #include "nvl_impl.h"
+ 
+-static int unpack_nvl(const struct nvunpackops *ops, const struct buffer *buffer,
+-		      struct nvlist *nvl)
+-{
+-	return -ENOTSUP; /* FIXME */
+-}
+-
+ struct nvlist *nvl_unpack(const void *ptr, size_t len, enum nvformat format)
+ {
+-	const struct buffer buffer = {
+-		.data = (void *) ptr,
+-		.used = len,
+-		.allocsize = len,
+-	};
+ 	const struct nvops *ops;
+-	struct nvlist *nvl;
+-	int ret;
++	struct buffer buffer;
++
++	buffer_init_const(&buffer, ptr, len);
+ 
+ 	ops = select_ops(format);
+-	if (!ops)
++	if (!ops || !ops->unpack.parse)
+ 		return ERR_PTR(-ENOTSUP);
+ 
+-	nvl = nvl_alloc();
+-	if (!nvl)
+-		return ERR_PTR(-ENOMEM);
+-
+-	ret = unpack_nvl(&ops->unpack, &buffer, nvl);
+-	if (!ret)
+-		return nvl;
+-
+-	nvl_putref(nvl);
+-	return ERR_PTR(ret);
++	return ops->unpack->parse(&buffer);
+ }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2020-01-08-2-WIP__nvlist__subtype_support.patch	Wed Jan 08 15:37:43 2020 -0500
@@ -0,0 +1,26 @@
+# HG changeset patch
+# User Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Date 1499287657 -10800
+#      Wed Jul 05 23:47:37 2017 +0300
+# Node ID a97ed990f62847abafdebc616180989bba17c39b
+# Parent  09eb09529f368dd34076f81cc4bfb3f61f47299c
+# EXP-Topic nvlist-unpack-framework
+WIP: nvlist: subtype support
+
+Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+
+diff --git a/include/jeffpc/nvl.h b/include/jeffpc/nvl.h
+--- a/include/jeffpc/nvl.h
++++ b/include/jeffpc/nvl.h
+@@ -45,6 +45,11 @@ enum nvtype {
+ 	NVT_STR,
+ };
+ 
++enum nvsubtype {
++	NVST_TIME,
++	NVST_BYTES, /* TODO: does this make sense to have? */
++};
++
+ /* serialization formats */
+ enum nvformat {
+ 	NVF_CBOR,	/* RFC 7049 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2020-01-08-3-WIP__nvlist__sexpr_packing.patch	Wed Jan 08 15:37:43 2020 -0500
@@ -0,0 +1,142 @@
+# HG changeset patch
+# User Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Date 1504011466 -10800
+#      Tue Aug 29 15:57:46 2017 +0300
+# Node ID fece0dcaf90f8cb0580eb0f084eaf08921f86dac
+# Parent  dc73a1af970bb6b4846a2c68659ee0c866f73d99
+# EXP-Topic nvlist-sexpr-packing
+WIP: nvlist: sexpr packing
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -98,6 +98,7 @@ add_library(jeffpc SHARED
+ 	nvl_dump.c
+ 	nvl_fmt_cbor.c
+ 	nvl_fmt_json.c
++	nvl_fmt_sexpr.c
+ 	nvl_pack.c
+ 	nvl_unpack.c
+ 	padding.c
+diff --git a/include/jeffpc/nvl.h b/include/jeffpc/nvl.h
+--- a/include/jeffpc/nvl.h
++++ b/include/jeffpc/nvl.h
+@@ -49,6 +49,7 @@ enum nvtype {
+ enum nvformat {
+ 	NVF_CBOR,	/* RFC 7049 */
+ 	NVF_JSON,	/* RFC 7159 */
++	NVF_SEXPR,
+ };
+ 
+ /* do not access these directly */
+diff --git a/nvl_fmt_sexpr.c b/nvl_fmt_sexpr.c
+new file mode 100644
+--- /dev/null
++++ b/nvl_fmt_sexpr.c
+@@ -0,0 +1,86 @@
++/*
++ * Copyright (c) 2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++
++#include <jeffpc/nvl.h>
++
++#include "nvl_impl.h"
++
++/*
++ * nvl packing interface
++ */
++
++static int sexpr_nvl_prologue(struct buffer *buffer, struct nvlist *nvl)
++{
++	return buffer_append_c(buffer, '(');
++}
++
++static int sexpr_nvl_epilogue(struct buffer *buffer, struct nvlist *nvl)
++{
++	return buffer_append_c(buffer, ')');
++}
++
++static int sexpr_array_prologue(struct buffer *buffer, const struct nvval *vals,
++			       size_t nelem)
++{
++	return buffer_append_c(buffer, '(');
++}
++
++static int sexpr_array_epilogue(struct buffer *buffer, const struct nvval *vals,
++			       size_t nelem)
++{
++	return buffer_append_c(buffer, ')');
++}
++
++static int sexpr_val_bool(struct buffer *buffer, bool b)
++{
++	return buffer_append_cstr(buffer, b ? "#t" : "#f");
++}
++
++static int sexpr_val_int(struct buffer *buffer, uint64_t i)
++{
++	return -ENOTSUP; // TODO: we need to support ints
++}
++
++static int sexpr_val_null(struct buffer *buffer)
++{
++	return buffer_append_cstr(buffer, "#n");
++}
++
++static int sexpr_val_str(struct buffer *buffer, const char *str)
++{
++	return -ENOTSUP; // TODO: we need to support strings
++}
++
++const struct nvops nvops_sexpr = {
++	.pack = {
++		.nvl_prologue = sexpr_nvl_prologue,
++		.nvl_epilogue = sexpr_nvl_epilogue,
++
++		.array_prologue = sexpr_array_prologue,
++		.array_epilogue = sexpr_array_epilogue,
++
++		.val_bool = sexpr_val_bool,
++		.val_int = sexpr_val_int,
++		.val_null = sexpr_val_null,
++		.val_str = sexpr_val_str,
++	}
++};
+diff --git a/nvl_impl.h b/nvl_impl.h
+--- a/nvl_impl.h
++++ b/nvl_impl.h
+@@ -117,6 +117,7 @@ struct nvops {
+ 
+ extern const struct nvops nvops_cbor;
+ extern const struct nvops nvops_json;
++extern const struct nvops nvops_sexpr;
+ 
+ static inline const struct nvops *select_ops(enum nvformat format)
+ {
+@@ -125,6 +126,8 @@ static inline const struct nvops *select
+ 			return &nvops_cbor;
+ 		case NVF_JSON:
+ 			return &nvops_json;
++		case NVF_SEXPR:
++			return &nvops_sexpr;
+ 	}
+ 
+ 	return NULL;