Mercurial > libjeffpc > bad-patches
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;