// Copyright (C) 2016, 2017, 2018, 2019  Stefan Vargyas
// 
// This file is part of Json-Type.
// 
// Json-Type 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.
// 
// Json-Type 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 Json-Type.  If not, see <http://www.gnu.org/licenses/>.

#include "config.h"

#define _GNU_SOURCE
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <alloca.h>
#include <unistd.h>
#include <errno.h>
#include <dlfcn.h>
#include <time.h>

#ifndef CONFIG_GEN_LOOKUP_LONG_OPT
#define PCRE2_CODE_UNIT_WIDTH 8
#include "pcre2.h"
#endif

#include "json-ast.h"
#include "json-litex.h"
#include "int-traits.h"
#include "char-traits.h"
#include "ptr-traits.h"
#include "pool-alloc.h"
#include "pretty-print.h"
#include "file-buf.h"
#include "dyn-lib.h"
#include "va-args.h"
#include "su-size.h"
#include "common.h"
#include "error.h"
#include "lhash.h"

#define   FNV_HASH_BITS 32
#include "fnv-hash.h"

#define   JSON_FILTER_NAME json_litex
#include "json-filter.h"

#ifdef CONFIG_GEN_LOOKUP_LONG_OPT
#ifdef DEBUG
#define JSON_DEBUG
#else
#undef JSON_DEBUG
#endif
#endif

#ifdef JSON_DEBUG
#define PRINT_DEBUG_COND expr->debug
#include "debug.h"
#endif

#define ISASCII CHAR_IS_ASCII
#define ISALPHA CHAR_IS_ALPHA
#define ISUPPER CHAR_IS_UPPER
#define ISALNUM CHAR_IS_ALNUM
#define ISSPACE CHAR_IS_SPACE
#define ISDIGIT CHAR_IS_DIGIT

#define strufunc_(f, r, a)                \
    (                                     \
        STATIC(                           \
            TYPEOF_IS_CHAR_UCHAR_PTR(a)), \
        r str ## f(                       \
            (const char*) (a))            \
    )
#define strufunc2_(f, r, a, b, ...)       \
    (                                     \
        STATIC(                           \
            TYPEOF_IS_CHAR_UCHAR_PTR(a)), \
        STATIC(                           \
            TYPEOF_IS_CHAR_UCHAR_PTR(b)), \
        r str ## f(                       \
            (const char*) (a),            \
            (const char*) (b),            \
            ## __VA_ARGS__)               \
    )
#define strulen(a) \
        strufunc_(len, , a)
#define strucmp(a, b) \
        strufunc2_(cmp, , a, b)
#define strustr(a, b) \
        strufunc2_(str, \
            (const uchar_t*), a, b)
#define strlcmp(s, b, l)               \
    (                                  \
        STATIC(TYPEOF_IS_CHAR_PTR(s)), \
        STATIC(TYPEOF_IS_CHAR_PTR(b)), \
        STATIC(TYPEOF_IS_SIZET(l)),    \
        (strlen(s) == l) &&            \
        (memcmp(s, b, l) == 0)         \
    )
#define strlucmp(s, b, l) \
        strufunc2_(lcmp, , s, b, l)

enum { debug_bits = 4 };

#ifndef JSON_LITEX_TEST_EXPR

#define VERSION VERSION_ID(JSON_LITEX)

static const char program[] = STRINGIFY(PROGRAM);
static const char verdate[] = STRINGIFY(VERSION) " -- 2019-02-14 08:55"; // $ date +'%F %R'

static const char license[] =
"Copyright (C) 2016, 2017, 2018, 2019  Stefan Vargyas.\n"
"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n";

const char stdin_name[] = "<stdin>";

#endif // JSON_LITEX_TEST_EXPR

#ifdef JSON_DEBUG

enum json_litex_debug_class_t
{
    json_litex_debug_expr_class,
    json_litex_debug_lib_class,
    json_litex_debug_obj_class
};

#undef  CASE
#define CASE(n) \
    [json_litex_debug_ ## n ## _class] = 0

static size_t json_litex_debug_levels[] = {
    CASE(expr),
    CASE(lib),
    CASE(obj),
};

static size_t json_litex_debug_get_level(
    enum json_litex_debug_class_t class)
{
    size_t* p;

    if (!(p = ARRAY_NULL_ELEM_REF(
            json_litex_debug_levels, class)))
        INVALID_ARG("%d", class);

    return *p;
}

#ifndef JSON_LITEX_TEST_EXPR

static void json_litex_debug_set_level(
    enum json_litex_debug_class_t class,
    size_t level)
{
    size_t* p;

    if (!(p = ARRAY_NULL_ELEM_REF(
            json_litex_debug_levels, class)))
        INVALID_ARG("%d", class);

    *p = level;
}

#endif // JSON_LITEX_TEST_EXPR

#endif // JSON_DEBUG

#define JSON_LITEX_EXPR_TYPEOF_IS_NODE_(q, p) \
    TYPEOF_IS(p, q struct json_litex_expr_node_t*)

#define JSON_LITEX_EXPR_TYPEOF_IS_NODE(p) \
        JSON_LITEX_EXPR_TYPEOF_IS_NODE_(, p)

#define JSON_LITEX_EXPR_TYPEOF_IS_NODE_CONST(p) \
        JSON_LITEX_EXPR_TYPEOF_IS_NODE_(const, p)

#define JSON_LITEX_EXPR_NODE_IS_(q, p, n)              \
    (                                                  \
        STATIC(JSON_LITEX_EXPR_TYPEOF_IS_NODE_(q, p)), \
        (p)->type == json_litex_expr_node_ ## n        \
    )
#define JSON_LITEX_EXPR_NODE_IS(p, n) \
        JSON_LITEX_EXPR_NODE_IS_(, p, n)
#define JSON_LITEX_EXPR_NODE_IS_CONST(p, n) \
        JSON_LITEX_EXPR_NODE_IS_(const, p, n)

#define JSON_LITEX_EXPR_NODE_AS_(q, p, n)          \
    ({                                             \
        ASSERT(JSON_LITEX_EXPR_NODE_IS_(q, p, n)); \
        (p)->node.n;                               \
    })
#define JSON_LITEX_EXPR_NODE_AS(p, n) \
        JSON_LITEX_EXPR_NODE_AS_(, p, n)
#define JSON_LITEX_EXPR_NODE_AS_CONST(p, n) \
        JSON_LITEX_EXPR_NODE_AS_(const, p, n)

#define JSON_LITEX_EXPR_NODE_AS_JUMP_(q, p)          \
    (                                                \
          JSON_LITEX_EXPR_NODE_IS_(q, p, jump_false) \
        ? &(p)->node.jump_false                      \
        : JSON_LITEX_EXPR_NODE_IS_(q, p, jump_true)  \
        ? &(p)->node.jump_true                       \
        : ({ ASSERT(false); NULL; })                 \
    )
#define JSON_LITEX_EXPR_NODE_AS_JUMP(p) \
        JSON_LITEX_EXPR_NODE_AS_JUMP_(, p)
#define JSON_LITEX_EXPR_NODE_AS_JUMP_CONST(p) \
        JSON_LITEX_EXPR_NODE_AS_JUMP_(const, p)

union json_litex_expr_rex_codes_pack_t
{
    const struct json_litex_expr_rex_patterns_t*
                                     patterns;
    const struct json_litex_expr_rex_streams_t*
                                     streams;
};

#define JSON_LITEX_EXPR_TYPEOF_IS_REX_CODES_(q, p) \
    TYPEOF_IS(p, q struct json_litex_expr_rex_codes_t*)

#define JSON_LITEX_EXPR_TYPEOF_IS_REX_CODES(p) \
        JSON_LITEX_EXPR_TYPEOF_IS_REX_CODES_(, p)

#define JSON_LITEX_EXPR_TYPEOF_IS_REX_CODES_CONST(p) \
        JSON_LITEX_EXPR_TYPEOF_IS_REX_CODES_(const, p)

#define JSON_LITEX_EXPR_REX_CODES_IS_(q, p, n)              \
    (                                                       \
        STATIC(JSON_LITEX_EXPR_TYPEOF_IS_REX_CODES_(q, p)), \
        (p)->type == json_litex_expr_rex_codes_ ## n        \
    )
#define JSON_LITEX_EXPR_REX_CODES_IS(p, n) \
        JSON_LITEX_EXPR_REX_CODES_IS_(, p, n)
#define JSON_LITEX_EXPR_REX_CODES_IS_CONST(p, n) \
        JSON_LITEX_EXPR_REX_CODES_IS_(const, p, n)

#define JSON_LITEX_EXPR_REX_CODES_AS_(q, p, n)          \
    ({                                                  \
        ASSERT(JSON_LITEX_EXPR_REX_CODES_IS_(q, p, n)); \
        &(p)->code.n;                                   \
    })
#define JSON_LITEX_EXPR_REX_CODES_AS(p, n) \
        JSON_LITEX_EXPR_REX_CODES_AS_(, p, n)
#define JSON_LITEX_EXPR_REX_CODES_AS_CONST(p, n) \
        JSON_LITEX_EXPR_REX_CODES_AS_(const, p, n)

#define JSON_LITEX_EXPR_REX_CODES_AS_IF_(q, p, n) \
    (                                             \
        JSON_LITEX_EXPR_REX_CODES_IS_(q, p, n)    \
        ? &(p)->code.n : NULL                     \
    )
#define JSON_LITEX_EXPR_REX_CODES_AS_IF(p, n) \
        JSON_LITEX_EXPR_REX_CODES_AS_IF_(, p, n)
#define JSON_LITEX_EXPR_REX_CODES_AS_IF_CONST(p, n) \
        JSON_LITEX_EXPR_REX_CODES_AS_IF_(const, p, n)

struct json_litex_expr_rex_bytes_t
{
    bits_t owned: 1;
    uchar_t* buf;
    size_t size;
};

static void json_litex_expr_rex_bytes_init(
    struct json_litex_expr_rex_bytes_t* bytes,
    const struct json_litex_expr_rex_codes_t* codes)
{
    union json_litex_expr_rex_codes_pack_t p;

    memset(bytes, 0, sizeof(*bytes));

    if ((p.patterns =
            JSON_LITEX_EXPR_REX_CODES_AS_IF_CONST(
                codes, patterns))) {
        int32_t r;

        r = pcre2_serialize_encode(
                CONST_CAST(p.patterns->objs,
                    const pcre2_code*),
                p.patterns->size,
                &bytes->buf,
                &bytes->size,
                NULL);
        ASSERT(INT_AS_SIZE(r) == p.patterns->size);

        bytes->owned = true;
    }
    else
    if ((p.streams =
            JSON_LITEX_EXPR_REX_CODES_AS_IF_CONST(
                codes, streams))) {
        bytes->buf = CONST_CAST(
            p.streams->bytes, uchar_t);
        bytes->size =
            p.streams->size;

        bytes->owned = false;
    }
    else
        UNEXPECT_VAR("%d", codes->type);
}

static void json_litex_expr_rex_bytes_done(
    struct json_litex_expr_rex_bytes_t* bytes)
{
    if (bytes->owned && bytes->buf != NULL)
        pcre2_serialize_free(bytes->buf);
}

struct json_litex_value_t;
struct json_litex_expr_vm_t;

enum json_litex_expr_builtin_type_t
{
    json_litex_expr_builtin_func,
    json_litex_expr_builtin_num,
    json_litex_expr_builtin_str,
};

enum json_litex_expr_builtin_func_arg_type_t
{
    json_litex_expr_builtin_func_arg_num,
    json_litex_expr_builtin_func_arg_str
};

#define JSON_LITEX_EXPR_BUILTIN_FUNC_ARG_IS(a, n, k) \
    ({                                               \
        ASSERT(k < SIZE_BIT);                        \
        STATIC(TYPEOF_IS_SIZET(k));                  \
        (((size_t) json_litex_expr_builtin_func_arg_ \
            ## n) << k) == ((SZ(1) << k) & (a));     \
    })

struct json_litex_expr_builtin_func_t
{
    bool (*val)(
        struct json_litex_expr_vm_t*,
        const struct json_litex_value_t*,
        json_litex_expr_value_t*);
    bits_t is_bool: 1;
    size_t n_args;
    size_t args;
};

struct json_litex_expr_builtin_num_t
{
    size_t val;
};

struct json_litex_expr_builtin_str_t
{
    const uchar_t* val;
};

struct json_litex_expr_builtin_t
{
    enum json_litex_expr_builtin_type_t type;
    union {
        struct json_litex_expr_builtin_func_t func;
        struct json_litex_expr_builtin_num_t  num;
        struct json_litex_expr_builtin_str_t  str;
    } u;
    const uchar_t* name;
};

#define JSON_LITEX_EXPR_TYPEOF_IS_BUILTIN_(q, p) \
    TYPEOF_IS(p, q struct json_litex_expr_builtin_t*)

#define JSON_LITEX_EXPR_TYPEOF_IS_BUILTIN(p) \
        JSON_LITEX_EXPR_TYPEOF_IS_BUILTIN_(, p)

#define JSON_LITEX_EXPR_TYPEOF_IS_BUILTIN_CONST(p) \
        JSON_LITEX_EXPR_TYPEOF_IS_BUILTIN_(const, p)

#define JSON_LITEX_EXPR_BUILTIN_IS_(q, p, n)              \
    (                                                     \
        STATIC(JSON_LITEX_EXPR_TYPEOF_IS_BUILTIN_(q, p)), \
        (p)->type == json_litex_expr_builtin_ ## n        \
    )
#define JSON_LITEX_EXPR_BUILTIN_IS(p, n) \
        JSON_LITEX_EXPR_BUILTIN_IS_(, p, n)
#define JSON_LITEX_EXPR_BUILTIN_IS_CONST(p, n) \
        JSON_LITEX_EXPR_BUILTIN_IS_(const, p, n)

#define JSON_LITEX_EXPR_BUILTIN_AS_(q, p, n)          \
    ({                                                \
        ASSERT(JSON_LITEX_EXPR_BUILTIN_IS_(q, p, n)); \
        &((p)->u.n);                                  \
    })
#define JSON_LITEX_EXPR_BUILTIN_AS(p, n) \
        JSON_LITEX_EXPR_BUILTIN_AS_(, p, n)
#define JSON_LITEX_EXPR_BUILTIN_AS_CONST(p, n) \
        JSON_LITEX_EXPR_BUILTIN_AS_(const, p, n)

struct json_litex_expr_flat_sizes_t
{
    size_t stack_max;
    size_t stack_init;
};

struct json_litex_expr_ast_sizes_t
{
    size_t pool_size;
    size_t stack_max;
    size_t stack_init;
};

struct json_litex_expr_sizes_t
{
    struct json_litex_expr_flat_sizes_t flat;
    struct json_litex_expr_ast_sizes_t  ast;
    size_t nodes_max;
    size_t nodes_init;
    size_t rexes_max;
    size_t rexes_init;
};

#define JSON_LITEX_EXPR_FLAT_SIZES_VALIDATE_(o)        \
    do {                                               \
        JSON_SIZE_VALIDATE(o stack_max);               \
        JSON_SIZE_VALIDATE(o stack_init);              \
    } while (0)
#define JSON_LITEX_EXPR_AST_SIZES_VALIDATE_(o)         \
    do {                                               \
        JSON_SIZE_VALIDATE(o pool_size);               \
        JSON_SIZE_VALIDATE(o stack_max);               \
        JSON_SIZE_VALIDATE(o stack_init);              \
    } while (0)
#define JSON_LITEX_EXPR_SIZES_VALIDATE_(o)             \
    do {                                               \
        JSON_LITEX_EXPR_FLAT_SIZES_VALIDATE_(o flat.); \
        JSON_LITEX_EXPR_AST_SIZES_VALIDATE_(o ast.);   \
        JSON_SIZE_VALIDATE(o nodes_max);               \
        JSON_SIZE_VALIDATE(o nodes_init);              \
        JSON_SIZE_VALIDATE(o rexes_max);               \
        JSON_SIZE_VALIDATE(o rexes_init);              \
    } while (0)
#define JSON_LITEX_EXPR_SIZES_VALIDATE(p)              \
    do {                                               \
        STATIC(JSON_TYPEOF_IS_SIZES(p, litex_expr));   \
        JSON_LITEX_EXPR_SIZES_VALIDATE_(p->);          \
    } while (0)

struct json_litex_expr_range_t
{
    const uchar_t* beg;
    const uchar_t* end;
};

enum json_litex_expr_token_type_t
{
    json_litex_expr_token_type_bos    = SZ(1) << 0,
    json_litex_expr_token_type_eos    = SZ(1) << 1,
    json_litex_expr_token_type_or     = SZ(1) << 2,
    json_litex_expr_token_type_and    = SZ(1) << 3,
    json_litex_expr_token_type_not    = SZ(1) << 4,
    json_litex_expr_token_type_eq     = SZ(1) << 5,
    json_litex_expr_token_type_ne     = SZ(1) << 6,
    json_litex_expr_token_type_lt     = SZ(1) << 7,
    json_litex_expr_token_type_le     = SZ(1) << 8,
    json_litex_expr_token_type_gt     = SZ(1) << 9,
    json_litex_expr_token_type_ge     = SZ(1) << 10,
    json_litex_expr_token_type_comma  = SZ(1) << 11,
    json_litex_expr_token_type_lparen = SZ(1) << 12,
    json_litex_expr_token_type_rparen = SZ(1) << 13,
    json_litex_expr_token_type_pound  = SZ(1) << 14,
    json_litex_expr_token_type_num    = SZ(1) << 15,
    json_litex_expr_token_type_str    = SZ(1) << 16,
    json_litex_expr_token_type_rex    = SZ(1) << 17,
    json_litex_expr_token_type_id     = SZ(1) << 18,
};

// stev: assuming that there aren't
// more than CHAR_BIT regex options:
#define JSON_LITEX_HASH_OPTS(h, o)   \
    (                                \
        STATIC(TYPEOF_IS_SIZET(o)),  \
        FNV_HASH_ADD(h, (uchar_t) o) \
    )

struct json_litex_expr_token_rex_value_t
{
    size_t     opts;
    fnv_hash_t hash;
};

union json_litex_expr_token_value_t
{
    struct json_litex_expr_token_rex_value_t
                            rex;
    json_litex_expr_value_t num;
};

struct json_litex_expr_token_t
{
    enum json_litex_expr_token_type_t   type;
    struct json_litex_expr_range_t      lexeme;
    union json_litex_expr_token_value_t value;
    struct json_text_pos_t              pos;
};

enum json_litex_expr_error_type_t
{
    json_litex_expr_error_none,
    json_litex_expr_error_unexpected_char,
    json_litex_expr_error_unexpected_token,
    json_litex_expr_error_unexpected_end_of_expr,
    json_litex_expr_error_empty_string,
    json_litex_expr_error_empty_regex,
    json_litex_expr_error_unknown_regex_opt,
    json_litex_expr_error_dup_regex_opt,
    json_litex_expr_error_invalid_number,
    json_litex_expr_error_invalid_operator,
    json_litex_expr_error_unknown_builtin,
    json_litex_expr_error_builtin_not_val,
    json_litex_expr_error_builtin_not_func,
    json_litex_expr_error_builtin_func_n_args,
    json_litex_expr_error_builtin_func_arg_type,
    json_litex_expr_error_pcre2_compile,
};

struct json_litex_expr_error_t
{
    enum json_litex_expr_error_type_t type;
    union {
        struct {
            const struct json_litex_expr_builtin_t* builtin;
        } builtin_not_val,
          builtin_not_func;
        struct {
            const struct json_litex_expr_builtin_t* builtin;
            size_t arg;
        } builtin_func_n_args,
          builtin_func_arg_type;
        int pcre2;
    };
    struct json_error_pos_t pos;
};

#define JSON_LITEX_EXPR_ERROR_IS(e) \
    (                               \
        expr->error.type ==         \
        json_litex_expr_error_ ## e \
    )
#define JSON_LITEX_EXPR_ERROR_(e, p, r) \
    ({                                  \
        expr->error.type = e;           \
        expr->error.pos = p;            \
        r;                              \
    })
#define JSON_LITEX_EXPR_ERROR(e, p) \
    JSON_LITEX_EXPR_ERROR_(         \
        json_litex_expr_error_ ##   \
        e, p, NULL)

#define JSON_LITEX_EXPR_BUILTIN_ERROR(b, e, p)   \
    ({                                           \
        expr->error.type =                       \
            json_litex_expr_error_builtin_ ## e; \
        expr->error.builtin_ ## e.builtin = b;   \
        expr->error.pos = p;                     \
        NULL;                                    \
    })

#define JSON_LITEX_EXPR_BUILTIN_FUNC_ERROR(b, e, p, a) \
    ({                                                 \
        expr->error.type =                             \
            json_litex_expr_error_builtin_func_ ## e;  \
        expr->error.builtin_func_ ## e.builtin = b;    \
        expr->error.builtin_func_ ## e.arg = a;        \
        expr->error.pos = p;                           \
        NULL;                                          \
    })

#define JSON_LITEX_EXPR_PCRE2_ERROR(e, c, p)   \
    ({                                         \
        expr->error.type =                     \
            json_litex_expr_error_pcre2_ ## e; \
        expr->error.pcre2 = c;                 \
        expr->error.pos = p;                   \
        false;                                 \
    })

#undef  VECTOR_NAME
#define VECTOR_NAME json_litex_expr_codes
#undef  VECTOR_ELEM_TYPE
#define VECTOR_ELEM_TYPE const pcre2_code*

#include "vector-impl.h"

static inline
    json_litex_expr_rex_index_t
    json_litex_expr_codes_vector_append(
        struct json_litex_expr_codes_vector_t* vect,
        pcre2_code* code)
{
    size_t r;

    r = VECTOR_APPEND(vect, code);

    STATIC(TYPES_COMPATIBLE(
        json_litex_expr_rex_index_t, uint16_t));
    ASSERT(r <= UINT16_MAX);

    return r;
}

#define LHASH_NAME json_litex_expr_rexes
#define LHASH_KEY_TYPE struct json_litex_expr_rex_t
#define LHASH_HASH_TYPE fnv_hash_t
#define LHASH_VAL_TYPE uint16_t

#define LHASH_NULL_KEY                    \
    (                                     \
        (struct json_litex_expr_rex_t) {  \
            .text = NULL, .opts = 0       \
        }                                 \
    )
#define LHASH_KEY_IS_NULL(x)                \
    (                                       \
        STATIC(TYPEOF_IS(x,                 \
            struct json_litex_expr_rex_t)), \
        x.text == NULL                      \
    )
#define LHASH_KEY_EQ(x, y)                  \
    (                                       \
        STATIC(TYPEOF_IS(x,                 \
            struct json_litex_expr_rex_t)), \
        STATIC(TYPEOF_IS(y,                 \
            struct json_litex_expr_rex_t)), \
        x.opts == y.opts &&                 \
        !strucmp(x.text, y.text)            \
    )
#define LHASH_KEY_HASH(x)                   \
    ({                                      \
        fnv_hash_t __r;                     \
        STATIC(TYPEOF_IS(x,                 \
            struct json_litex_expr_rex_t)); \
        __r = FNV_HASH_DIGEST(x.text);      \
        JSON_LITEX_HASH_OPTS(__r, x.opts);  \
    })

#include "lhash-impl.h"

#undef  VECTOR_NAME
#define VECTOR_NAME json_litex_expr_nodes
#undef  VECTOR_ELEM_TYPE
#define VECTOR_ELEM_TYPE struct json_litex_expr_node_t

#define VECTOR_NEED_CLEAR
#define VECTOR_NEED_ROTATE_ELEM
#include "vector-impl.h"
#undef  VECTOR_NEED_ROTATE_ELEM
#undef  VECTOR_NEED_CLEAR

typedef size_t json_litex_expr_jump_list_t;

struct json_litex_expr_flat_attr_t
{
    bits_t                      is_bool: 1;
    json_litex_expr_jump_list_t false_list;
    json_litex_expr_jump_list_t true_list;
    json_litex_expr_jump_list_t jump;
    struct json_text_pos_t      pos;
    size_t                      ins;
};

#define STACK_NAME      json_litex_expr_flat
#define STACK_ELEM_TYPE struct json_litex_expr_flat_attr_t

#define STACK_NEED_MAX_SIZE
#define STACK_NEED_STRUCT_ONLY
#include "stack-impl.h"
#undef  STACK_NEED_STRUCT_ONLY
#undef  STACK_NEED_MAX_SIZE

struct json_litex_expr_flat_impl_t
{
    //struct json_litex_expr_flat_sizes_t sizes;
    struct json_litex_expr_flat_stack_t stack;
};

struct json_litex_expr_ast_attr_t
{
    bits_t                      is_bool: 1;
    json_litex_expr_jump_list_t false_list;
    json_litex_expr_jump_list_t true_list;
};

#undef  STACK_NAME
#define STACK_NAME      json_litex_expr_ast
#undef  STACK_ELEM_TYPE
#define STACK_ELEM_TYPE struct json_litex_expr_ast_node_t*

#define STACK_NEED_MAX_SIZE
#define STACK_NEED_STRUCT_ONLY
#include "stack-impl.h"
#undef  STACK_NEED_STRUCT_ONLY
#undef  STACK_NEED_MAX_SIZE

enum json_litex_expr_ast_bin_op_t
{
    json_litex_expr_ast_bin_op_eq,
    json_litex_expr_ast_bin_op_ne,
    json_litex_expr_ast_bin_op_lt,
    json_litex_expr_ast_bin_op_le,
    json_litex_expr_ast_bin_op_gt,
    json_litex_expr_ast_bin_op_ge,
    json_litex_expr_ast_bin_op_and,
    json_litex_expr_ast_bin_op_or
};

enum json_litex_expr_ast_node_type_t
{
    json_litex_expr_ast_node_const_id,
    json_litex_expr_ast_node_const_num,
    json_litex_expr_ast_node_const_str,
    json_litex_expr_ast_node_count_str,
    json_litex_expr_ast_node_count_rex,
    json_litex_expr_ast_node_match_str,
    json_litex_expr_ast_node_match_rex,
    json_litex_expr_ast_node_call_builtin,
    json_litex_expr_ast_node_bin_op,
    json_litex_expr_ast_node_not_op,
};

struct json_litex_expr_ast_call_builtin_node_t
{
    const struct json_litex_expr_builtin_t* builtin;
    struct json_litex_expr_ast_node_t**     args;
};

struct json_litex_expr_ast_binary_node_t
{
    struct json_litex_expr_ast_node_t* left;
    struct json_litex_expr_ast_node_t* right;
    enum json_litex_expr_ast_bin_op_t  op;
};

struct json_litex_expr_ast_unary_node_t
{
    struct json_litex_expr_ast_node_t* node;
    //enum json_litex_expr_ast_un_op_t op;
};

typedef json_litex_expr_value_t
    json_litex_expr_ast_num_node_t;
typedef json_litex_expr_string_t
    json_litex_expr_ast_str_node_t;
typedef json_litex_expr_rex_index_t
    json_litex_expr_ast_rex_node_t;
typedef const struct json_litex_expr_builtin_t*
    json_litex_expr_ast_btv_node_t;
typedef struct json_litex_expr_ast_call_builtin_node_t
    json_litex_expr_ast_btf_node_t;
typedef struct json_litex_expr_ast_binary_node_t
    json_litex_expr_ast_bin_node_t;
typedef struct json_litex_expr_ast_unary_node_t
    json_litex_expr_ast_not_node_t;

struct json_litex_expr_ast_node_t
{
    enum json_litex_expr_ast_node_type_t type;
    union {
        json_litex_expr_ast_btv_node_t const_id;
        json_litex_expr_ast_num_node_t const_num;
        json_litex_expr_ast_str_node_t const_str;
        json_litex_expr_ast_str_node_t count_str;
        json_litex_expr_ast_rex_node_t count_rex;
        json_litex_expr_ast_str_node_t match_str;
        json_litex_expr_ast_rex_node_t match_rex;
        json_litex_expr_ast_btf_node_t call_builtin;
        json_litex_expr_ast_bin_node_t bin_op;
        json_litex_expr_ast_not_node_t not_op;
    };
    struct json_text_pos_t pos;
};

#define JSON_LITEX_EXPR_AST_TYPEOF_IS_NODE_(q, p) \
    TYPEOF_IS(p, q struct json_litex_expr_ast_node_t*)

#define JSON_LITEX_EXPR_AST_TYPEOF_IS_NODE(p) \
        JSON_LITEX_EXPR_AST_TYPEOF_IS_NODE_(, p)

#define JSON_LITEX_EXPR_AST_TYPEOF_IS_NODE_CONST(p) \
        JSON_LITEX_EXPR_AST_TYPEOF_IS_NODE_(const, p)

#define JSON_LITEX_EXPR_AST_NODE_IS_(q, p, n)              \
    (                                                      \
        STATIC(JSON_LITEX_EXPR_AST_TYPEOF_IS_NODE_(q, p)), \
        (p)->type == json_litex_expr_ast_node_ ## n        \
    )
#define JSON_LITEX_EXPR_AST_NODE_IS(p, n) \
        JSON_LITEX_EXPR_AST_NODE_IS_(, p, n)
#define JSON_LITEX_EXPR_AST_NODE_IS_CONST(p, n) \
        JSON_LITEX_EXPR_AST_NODE_IS_(const, p, n)

#define JSON_LITEX_EXPR_AST_NODE_AS_(q, p, n)          \
    ({                                                 \
        ASSERT(JSON_LITEX_EXPR_AST_NODE_IS_(q, p, n)); \
        &(p)->node.n;                                  \
    })
#define JSON_LITEX_EXPR_AST_NODE_AS(p, n) \
        JSON_LITEX_EXPR_AST_NODE_AS_(, p, n)
#define JSON_LITEX_EXPR_AST_NODE_AS_CONST(p, n) \
        JSON_LITEX_EXPR_AST_NODE_AS_(const, p, n)

struct json_litex_expr_ast_impl_t
{
    //struct json_litex_expr_ast_sizes_t sizes;
    struct pool_alloc_t                pool;
    struct json_litex_expr_ast_node_t* root;
    struct json_litex_expr_ast_stack_t stack;
};

enum json_litex_expr_impl_type_t
{
    json_litex_expr_flat_impl_type,
    json_litex_expr_ast_impl_type,
};

enum json_litex_expr_opts_t
{
    json_litex_expr_opts_keep_regex_texts = 1 << 0,
#ifdef JSON_LITEX_TEST_EXPR
    json_litex_expr_opts_alpha_builtins   = 1 << 1,
#endif // JSON_LITEX_TEST_EXPR
};

#define JSON_LITEX_EXPR_OPTS_(n) \
        json_litex_expr_opts_ ## n
#define JSON_LITEX_EXPR_OPTS(...) \
    (VA_ARGS_REPEAT(|, JSON_LITEX_EXPR_OPTS_, __VA_ARGS__))

#define JSON_LITEX_EXPR_OPTS_IS__(n) \
    (expr->opts & JSON_LITEX_EXPR_OPTS_(n))
#define JSON_LITEX_EXPR_OPTS_IS_(o, ...) \
    (VA_ARGS_REPEAT(o, JSON_LITEX_EXPR_OPTS_IS__, __VA_ARGS__))
#define JSON_LITEX_EXPR_OPTS_IS(...) \
        JSON_LITEX_EXPR_OPTS_IS_(&&, __VA_ARGS__)

struct json_litex_expr_t;

typedef void (*json_litex_expr_impl_func_t)(void*);

typedef bool (*json_litex_expr_num_rule_t)(
    struct json_litex_expr_t*, void*,
    const struct json_text_pos_t*,
    json_litex_expr_value_t);
typedef bool (*json_litex_expr_str_rule_t)(
    struct json_litex_expr_t*, void*,
    const struct json_text_pos_t*,
    const uchar_t*, size_t);
typedef bool (*json_litex_expr_rex_rule_t)(
    struct json_litex_expr_t*, void*,
    const struct json_text_pos_t*,
    const uchar_t*, size_t, size_t,
    fnv_hash_t);
typedef bool (*json_litex_expr_btv_rule_t)(
    struct json_litex_expr_t*, void*,
    const struct json_text_pos_t*,
    const uchar_t*, size_t);
typedef bool (*json_litex_expr_btf_rule_t)(
    struct json_litex_expr_t*, void*,
    const struct json_text_pos_t*,
    const uchar_t*, size_t, size_t);
typedef bool (*json_litex_expr_cmp_rule_t)(
    struct json_litex_expr_t*, void*,
    const struct json_text_pos_t*,
    enum json_litex_expr_cmp_op_t);
typedef bool (*json_litex_expr_nul_rule_t)(
    struct json_litex_expr_t*, void*,
    const struct json_text_pos_t*);
typedef bool (*json_litex_expr_top_rule_t)(
    struct json_litex_expr_t*, void*);

struct json_litex_expr_impl_t
{
    enum json_litex_expr_impl_type_t       type;
    union {
        struct json_litex_expr_flat_impl_t flat;
        struct json_litex_expr_ast_impl_t  ast;
    };
    json_litex_expr_impl_func_t clear_func;
    json_litex_expr_impl_func_t done_func;

    json_litex_expr_btv_rule_t  const_id_rule;
    json_litex_expr_num_rule_t  const_num_rule;
    json_litex_expr_str_rule_t  const_str_rule;
    json_litex_expr_str_rule_t  count_str_rule;
    json_litex_expr_rex_rule_t  count_rex_rule;
    json_litex_expr_str_rule_t  match_str_rule;
    json_litex_expr_rex_rule_t  match_rex_rule;
    json_litex_expr_btf_rule_t  call_builtin_rule;
    json_litex_expr_nul_rule_t  cmp_in_rule;
    json_litex_expr_cmp_rule_t  cmp_op_rule;
    json_litex_expr_nul_rule_t  not_op_rule;
    json_litex_expr_nul_rule_t  and_in_rule;
    json_litex_expr_nul_rule_t  and_op_rule;
    json_litex_expr_nul_rule_t  or_in_rule;
    json_litex_expr_nul_rule_t  or_op_rule;
    json_litex_expr_top_rule_t  top_rule;
};

union json_litex_expr_impl_pack_t
{
    struct json_litex_expr_flat_impl_t* flat;
    struct json_litex_expr_ast_impl_t*  ast;
};

typedef void* (*json_litex_expr_alloc_func_t)(
    void* obj, size_t size, size_t align);
typedef bool (*json_litex_expr_dealloc_func_t)(
    void* obj, void* ptr);

typedef const struct json_litex_expr_builtin_t*
    (*json_litex_expr_lookup_builtin_func_t)(
        struct json_litex_expr_t*,
        const uchar_t*, size_t);

typedef void (*json_litex_expr_update_pos_func_t)(
    struct json_litex_expr_t*,
    size_t);

struct json_litex_expr_t
{
#ifdef JSON_DEBUG
    bits_t                                debug:
                                          debug_bits;
#endif
    bits_t                                is_raw: 1;
    enum json_litex_expr_opts_t           opts;
#ifdef JSON_LITEX_TEST_EXPR
    json_litex_expr_lookup_builtin_func_t lookup;
#endif // JSON_LITEX_TEST_EXPR
    void*                                 alloc_obj;
    json_litex_expr_alloc_func_t          alloc_func;
    json_litex_expr_dealloc_func_t        dealloc_func;
    pcre2_general_context*                pcre2_gc;
    pcre2_compile_context*                pcre2_cc;
    struct json_litex_expr_impl_t         impl;
    struct json_litex_expr_range_t        input;
    const uchar_t*                        input_ptr;
    struct json_text_pos_t                input_pos;
    json_litex_expr_update_pos_func_t     update_pos;
    struct json_litex_expr_token_t        token;
    struct json_litex_expr_token_t        prev_tok;
    struct json_litex_expr_error_t        error;
    struct json_litex_expr_nodes_vector_t nodes;
    struct json_litex_expr_codes_vector_t codes;
    struct json_litex_expr_rexes_lhash_t  rexes;
    const
    struct json_litex_expr_rex_def_t*     rdef;
    struct json_litex_expr_def_t*         def;
};

#undef  CASE
#define CASE(n) \
    [json_litex_expr_node_ ## n] = #n

static const char* json_litex_expr_node_type_get_name(
    enum json_litex_expr_node_type_t type)
{
    static const char* const types[] = {
        CASE(const_id),
        CASE(const_num),
        CASE(const_str),
        CASE(count_str),
        CASE(count_rex),
        CASE(match_str),
        CASE(match_rex),
        CASE(call_builtin),
        CASE(jump_false),
        CASE(jump_true),
        CASE(make_bool),
        CASE(cmp_op),
        CASE(not_op),
    };
    const char* n = ARRAY_NULL_ELEM(
        types, type);

    if (n == NULL)
        UNEXPECT_VAR("%d", type);

    return n;
}

static inline const char*
    json_litex_expr_node_get_name_suffix(
        const struct json_litex_expr_node_t* node)
{
    return
        (JSON_LITEX_EXPR_NODE_IS_CONST(
            node, jump_false) &&
            node->node.jump_false.popu) ||
        (JSON_LITEX_EXPR_NODE_IS_CONST(
            node, jump_true) &&
            node->node.jump_true.popu)
        ? "*" : "";
}

#undef  CASE
#define CASE(n) \
    [json_litex_expr_cmp_op_ ## n] = #n

static const char* json_litex_expr_cmp_op_get_name(
    enum json_litex_expr_cmp_op_t op)
{
    static const char* const ops[] = {
        CASE(eq),
        CASE(ne),
        CASE(lt),
        CASE(le),
        CASE(gt),
        CASE(ge),
    };
    const char* n = ARRAY_NULL_ELEM(
        ops, op);

    if (n == NULL)
        UNEXPECT_VAR("%d", op);

    return n;
}

#define JSON_LITEX_EXPR_REX_OPT_SET(o, n) \
    (                                     \
        STATIC(TYPEOF_IS_SIZET(o)),       \
        STATIC(TYPEOF_IS_INTEGER(         \
            json_litex_expr_rex_opt_      \
                ## n)),                   \
        STATIC(                           \
            json_litex_expr_rex_opt_      \
                ## n >= 0 &&              \
            json_litex_expr_rex_opt_      \
                ## n < SIZE_BIT),         \
        (o) & (SZ(1) <<                   \
            json_litex_expr_rex_opt_      \
                ## n)                     \
    )

#define JSON_LITEX_EXPR_REX_PRINT_OPT(n)          \
    do {                                          \
        if (JSON_LITEX_EXPR_REX_OPT_SET(          \
                rex->opts, n))                    \
            fputs(json_litex_expr_rex_opt_names[  \
                  json_litex_expr_rex_opt_ ## n], \
                file);                            \
    } while (0)

#undef  CASE
#define CASE(n, c) \
    [json_litex_expr_rex_opt_ ## n] = #c

static const char* const
    json_litex_expr_rex_opt_names[] = {
        CASE(ignore_case, I),
        CASE(dot_match_all, S),
        CASE(no_utf8_check, U),
        CASE(extended_pat, X),
    };

#ifndef JSON_LITEX_TEST_EXPR

static void json_litex_expr_rex_print(
    const struct json_litex_expr_rex_t* rex,
    FILE* file)
{
    fputs("{\"text\":", file);

    pretty_print_repr(file,
        rex->text, strulen(rex->text),
        PRETTY_PRINT_REPR_FLAGS(
            quote_non_print,
            print_quotes));

    fputs(",\"opts\":\"", file);

    JSON_LITEX_EXPR_REX_PRINT_OPT(ignore_case);
    JSON_LITEX_EXPR_REX_PRINT_OPT(dot_match_all);
    JSON_LITEX_EXPR_REX_PRINT_OPT(no_utf8_check);
    JSON_LITEX_EXPR_REX_PRINT_OPT(extended_pat);

    fputs("\"}", file);
}

#undef  CASE
#define CASE(n, m, ...)              \
    case json_litex_expr_node_ ## n: \
        m(n, ## __VA_ARGS__);        \
        break

static void json_litex_expr_node_print(
    const struct json_litex_expr_node_t* node,
    FILE* file)
{
    fprintf(file, "{\"type\":\"%s%s\"",
        json_litex_expr_node_type_get_name(node->type),
        json_litex_expr_node_get_name_suffix(node));

    if (node->type != json_litex_expr_node_make_bool &&
        node->type != json_litex_expr_node_not_op)
        fputs(",\"val\":", file);

#define NOP(n)
#define CMP(n)                           \
    fprintf(file, "\"%s\"",              \
        json_litex_expr_cmp_op_get_name( \
            node->node.n))
#define FMT(n, f) \
    fprintf(file, "%" #f, node->node.n)
#define JMP(n) \
    FMT(n.offs, d)
#define STR(n)                               \
    pretty_print_repr(file,                  \
        node->node.n, strulen(node->node.n), \
        PRETTY_PRINT_REPR_FLAGS(             \
            quote_non_print,                 \
            print_quotes))
#define BUILTIN(n) \
    fprintf(file, "\"%s\"", node->node.n->name)

    switch (node->type) {
    CASE(const_id, BUILTIN);
    CASE(const_num, FMT, zu);
    CASE(const_str, STR);
    CASE(count_str, STR);
    CASE(count_rex, FMT, d);
    CASE(match_str, STR);
    CASE(match_rex, FMT, d);
    CASE(call_builtin, BUILTIN);
    CASE(jump_false, JMP);
    CASE(jump_true, JMP);
    CASE(make_bool, NOP);
    CASE(cmp_op, CMP);
    CASE(not_op, NOP);
    default:
        UNEXPECT_VAR("%d", node->type);
    }
    fputc('}', file);

#undef BUILTIN
#undef STR
#undef JMP
#undef FMT
#undef CMP
#undef NOP
}

static void json_litex_expr_node_gen_def(
    const struct json_litex_expr_node_t* node,
    FILE* file)
{
    fprintf(file, "    %s(",
        json_litex_expr_node_type_get_name(node->type));

#define NOP(n)
#define CMP(n)                             \
    fputs(json_litex_expr_cmp_op_get_name( \
        node->node.n), file)
#define FMT(n, f) \
    fprintf(file, "%" #f, node->node.n)
#define JMP(n)                                 \
    fprintf(file, "%d, %s", node->node.n.offs, \
        node->node.n.popu ? "true" : "false")
#define STR(n)                               \
    pretty_print_repr(file,                  \
        node->node.n, strulen(node->node.n), \
        PRETTY_PRINT_REPR_FLAGS(             \
            quote_non_print,                 \
            print_quotes))
#define BUILTIN(n) \
    FMT(n->name, s)

    switch (node->type) {
    CASE(const_id, BUILTIN);
    CASE(const_num, FMT, zu);
    CASE(const_str, STR);
    CASE(count_str, STR);
    CASE(count_rex, FMT, d);
    CASE(match_str, STR);
    CASE(match_rex, FMT, d);
    CASE(call_builtin, BUILTIN);
    CASE(jump_false, JMP);
    CASE(jump_true, JMP);
    CASE(make_bool, NOP);
    CASE(cmp_op, CMP);
    CASE(not_op, NOP);
    default:
        UNEXPECT_VAR("%d", node->type);
    }

    fputc(')', file);

#undef BUILTIN
#undef STR
#undef JMP
#undef FMT
#undef CMP
#undef NOP
}

#endif // JSON_LITEX_TEST_EXPR

static void json_litex_expr_merge_jump_lists(
    struct json_litex_expr_t* expr,
    json_litex_expr_jump_list_t* list1,
    json_litex_expr_jump_list_t list2)
{
    struct json_litex_expr_node_t* n;
    struct json_litex_expr_jump_t* p;
    size_t i;

    ASSERT(*list1 <=
        VECTOR_N_ELEMS(&expr->nodes));
    ASSERT(list2 <=
        VECTOR_N_ELEMS(&expr->nodes));

    if (list2 == 0)
        return;
    if (*list1 == 0) {
        *list1 = list2;
        return;
    }

    ASSERT(*list1 != list2);

    // stev: get to the last entry of 'list2';
    // note that could have looked up for last
    // entry of 'list1', but for at least one
    // in two of its calls, this function gets
    // 'list2' as an one-element list only!
    i = list2;
    do {
        n = VECTOR_ELEM_REF(&expr->nodes, i - 1);
        p = JSON_LITEX_EXPR_NODE_AS_JUMP(n);
    } while ((i = p->offs));

    p->offs = *list1;
    *list1 = list2;
}

#define JSON_LITEX_EXPR_LAST_AS_LIST()            \
    ({                                            \
        ASSERT(expr->nodes.n_elems < UINT16_MAX); \
        (json_litex_expr_jump_list_t)             \
        expr->nodes.n_elems;                      \
    })

static void json_litex_expr_backpatch_jump_list(
    struct json_litex_expr_t* expr,
    json_litex_expr_jump_list_t list,
    bool pop_uncond)
{
    struct json_litex_expr_node_t* n;
    struct json_litex_expr_jump_t* p;
    size_t k, i, j;

    ASSERT(list <=
        VECTOR_N_ELEMS(&expr->nodes));

    // stev: nothing to do in
    // case of an empty list
    if (list == 0)
        return;

    i = list;
    j = JSON_LITEX_EXPR_LAST_AS_LIST() + 1;
    do {
        n = VECTOR_ELEM_REF(&expr->nodes, i - 1);
        p = JSON_LITEX_EXPR_NODE_AS_JUMP(n);
        k = p->offs;

        p->offs = SIZE_SUB(j, i);
        p->popu = pop_uncond;
    } while ((i = k));
}

static void json_litex_expr_backpatch_flat_attr(
    struct json_litex_expr_t* expr,
    const struct json_litex_expr_flat_attr_t* a)
{
    json_litex_expr_backpatch_jump_list(
        expr, a->false_list,
        false);
    json_litex_expr_backpatch_jump_list(
        expr, a->true_list,
        false);
}

static void json_litex_expr_backpatch_ast_attr(
    struct json_litex_expr_t* expr,
    const struct json_litex_expr_ast_attr_t* a)
{
    json_litex_expr_backpatch_jump_list(
        expr, a->false_list,
        false);
    json_litex_expr_backpatch_jump_list(
        expr, a->true_list,
        false);
}

#define JSON_LITEX_EXPR_ALLOCATE_STRUCT(t) \
    (                                      \
        expr->alloc_func(                  \
            expr->alloc_obj,               \
            sizeof(struct t),              \
            MEM_ALIGNOF(struct t))         \
    )

#define JSON_LITEX_EXPR_ALLOCATE_ARRAY(t, n) \
    ({                                       \
        STATIC(TYPEOF_IS_SIZET(n));          \
        expr->alloc_func(expr->alloc_obj,    \
            SIZE_MUL(n, sizeof(t)),          \
            MEM_ALIGNOF(t[0]));              \
    })

static inline struct json_litex_expr_node_t*
    json_litex_expr_new_node(
        struct json_litex_expr_t* expr)
{
    return VECTOR_APPEND_REF(&expr->nodes);
}

static json_litex_expr_string_t
    json_litex_expr_new_str(
        struct json_litex_expr_t* expr,
        const uchar_t* str, size_t len)
{
    const uchar_t *p, *q;
    size_t l, n, d;
    uchar_t *r, c;

    ASSERT(len > 2);
    ASSERT(str != NULL);
    ASSERT(str[len - 1] == str[0]);
    ASSERT(str[0]);

    c = *str ++;
    n = len -= 2;

    // stev: loop invariant:
    // p + l == str + len
    for (l = len,
         p = str;
        (q = memchr(p, c, l));
         p = q + 2) {
         d = PTR_DIFF(q + 1, p);
         SIZE_SUB_EQ(l, d + 1);
         ASSERT(q[1] == c);
         n --;
    }

    r = JSON_LITEX_EXPR_ALLOCATE_ARRAY(
            uchar_t, n + 1);

    if (n == len)
        goto copy_str;

    // stev: loop invariant:
    // p + l == str + len
    for (l = len,
         p = str;
        (q = memchr(p, c, l));
         p = q + 2) {
         d = PTR_DIFF(q + 1, p);
         SIZE_SUB_EQ(l, d + 1);
         memcpy(r, p, d);
         r += d;
    }

copy_str:
    memcpy(r, p, l);
    r += l;
    *r = 0;

    return r - n;
}

static void json_litex_expr_del_str(
    struct json_litex_expr_t* expr,
    json_litex_expr_string_t str)
{
    bool r;

    r = expr->dealloc_func(
            expr->alloc_obj,
            CONST_CAST(str, uchar_t));
    if (JSON_LITEX_EXPR_ERROR_IS(none))
        ASSERT(r);
}

static void json_litex_update_rex_pos_raw(
    struct json_text_pos_t* pos,
    const uchar_t* text, size_t len,
    uchar_t qchar)
{
    size_t d, n;

    ASSERT(qchar != '\n');

    d = json_text_update_pos(
            pos, text, len);
    if (len)
        ASSERT(d < len);
    else
        ASSERT(d == 0);

    n = json_text_count(
            text + d, len - d, qchar);
    SIZE_ADD_EQ(pos->col, n);
}

static const uchar_t
    json_litex_escapes[128] = {
        ['"'] = 1,  ['\\'] = 1,
        ['\b'] = 1, ['\f'] = 1,
        ['\n'] = 1, ['\r'] = 1,
        ['\t'] = 1
};

static void json_litex_update_rex_pos(
    struct json_text_pos_t* pos,
    const uchar_t* text, size_t len,
    uchar_t qchar)
{
    const uchar_t *p, *e;
    size_t n = 0;

    for (p = text,
         e = p + len;
         p < e;
         p ++) {
        if (*p == qchar)
            n ++;
        else
        if (ISASCII(*p))
            n += json_litex_escapes[*p];
    }

    SIZE_ADD_EQ(pos->col, len);
    SIZE_ADD_EQ(pos->col, n);
}

static bool json_litex_expr_compile_rex(
    struct json_litex_expr_t* expr,
    const struct json_text_pos_t* pos,
    struct json_litex_expr_rex_t rex,
    json_litex_expr_rex_index_t* rid,
    uchar_t qchar)
{
    uint32_t o = PCRE2_UTF;
    size_t n, w = 0;
    pcre2_code* c;
    int e = 0;

#define OPT(n) \
    JSON_LITEX_EXPR_REX_OPT_SET(rex.opts, n)
    if (OPT(ignore_case))
        o |= PCRE2_CASELESS;
    if (OPT(dot_match_all))
        o |= PCRE2_DOTALL;
    if (OPT(no_utf8_check))
        o |= PCRE2_NO_UTF_CHECK;
    if (OPT(extended_pat))
        o |= PCRE2_EXTENDED;
#undef OPT

    n = strulen(rex.text);
    ASSERT(n != PCRE2_ZERO_TERMINATED);

    if (!(c = pcre2_compile(
                rex.text, n, o, &e,
                &w, expr->pcre2_cc))) {
        struct json_text_pos_t p = *pos;

        // stev: take into account that
        // regexes start with '/' or '\'':
        ASSERT_SIZE_INC_NO_OVERFLOW(p.col);
        p.col ++;

        PRINT_DEBUG(
            "p={%zu,%zu} n=%zu e=%d w=%zu",
            p.line, p.col, n, e, w);

        // stev: PCRE2's compile function
        // produces 0-based error offsets;
        // but note that 'w' may very well
        // point at the end of regex 'text'
        ASSERT(w <= n);

        (expr->is_raw
            ? json_litex_update_rex_pos_raw
            : json_litex_update_rex_pos)(
            &p, rex.text, w, qchar);

        return JSON_LITEX_EXPR_PCRE2_ERROR(
            compile, e, p);
    }

    *rid =
        json_litex_expr_codes_vector_append(
            &expr->codes, c);

    return true;
}

static bool json_litex_expr_new_rex(
    struct json_litex_expr_t* expr,
    const struct json_text_pos_t* pos,
    const uchar_t* str, size_t len,
    size_t opts, fnv_hash_t hash,
    json_litex_expr_rex_index_t* rex)
{
    struct json_litex_expr_rexes_lhash_node_t* n;
    struct json_litex_expr_rex_t r;

    ASSERT(str != NULL);

    r.text = json_litex_expr_new_str(
        expr, str, len);
    r.opts = opts;

    // stev: update 'hash' to reflect
    // the key value in its entirety:
    JSON_LITEX_HASH_OPTS(hash, opts);

    if (!json_litex_expr_rexes_lhash_insert(
            &expr->rexes, r, hash, &n)) {
        json_litex_expr_del_str(
            expr, r.text);
        *rex = n->val;
        return true;
    }

    if (!json_litex_expr_compile_rex(
            expr, pos, r, &n->val, *str)) {
        json_litex_expr_rexes_lhash_rollback(
            &expr->rexes, n);
        json_litex_expr_del_str(
            expr, r.text);
        return false;
    }

    *rex = n->val;
    return true;
}

static struct json_litex_expr_def_t*
    json_litex_expr_new_def(
        struct json_litex_expr_t* expr)
{
    struct json_litex_expr_node_t* n;
    struct json_litex_expr_def_t* d;
    size_t s = expr->nodes.n_elems;

    n = JSON_LITEX_EXPR_ALLOCATE_ARRAY(
            struct json_litex_expr_node_t, s);
    d = JSON_LITEX_EXPR_ALLOCATE_STRUCT(
            json_litex_expr_def_t);

    memcpy(n, expr->nodes.elems,
        SIZE_MUL(s, sizeof(*expr->nodes.elems)));

    d->nodes = n;
    d->size = s;

    return d;
}

static struct json_litex_expr_rex_def_t*
    json_litex_expr_new_rex_def(
        struct json_litex_expr_t* expr)
{
    struct json_litex_expr_rexes_lhash_node_t *q, *e;
    struct json_litex_expr_rex_def_t* d;
    struct json_litex_expr_rex_t* r;
    const pcre2_code** p;
    size_t n, k;

    n = expr->codes.n_elems;
    ASSERT(expr->rexes.used == n);

    p = JSON_LITEX_EXPR_ALLOCATE_ARRAY(
            const pcre2_code*, n);
    memcpy(p, expr->codes.elems,
        SIZE_MUL(n, sizeof(*expr->codes.elems)));

    d = JSON_LITEX_EXPR_ALLOCATE_STRUCT(
            json_litex_expr_rex_def_t);
    d->codes.type = json_litex_expr_rex_codes_patterns;
    d->codes.code.patterns.size = n;
    d->codes.code.patterns.objs = p;
    d->size = n;

    if (!JSON_LITEX_EXPR_OPTS_IS(keep_regex_texts))
        return d;

    d->elems = r =
        JSON_LITEX_EXPR_ALLOCATE_ARRAY(
            struct json_litex_expr_rex_t, n);

    LHASH_ASSERT_INVARIANTS(&expr->rexes);

    for (k = 0,
         q = expr->rexes.table,
         e = q + expr->rexes.size;
         k < n;
         q ++) {
        ASSERT(q < e);
        if (q->key.text != NULL) {
            ASSERT(q->val < n);
            r[q->val] = q->key;
            k ++;
        }
    }

    return d;
}

#ifdef JSON_LITEX_TEST_EXPR

static size_t
    json_litex_expr_lookup_alpha_builtin_name(
        const uchar_t* name, size_t len)
{
    //  1  2  3  4  5  6  7  8  9 10
    // A1 B1 C1 A2 B2 A3 N1 N2 S1 S2
    ASSERT(len == 2);

    switch (*name ++) {
    case 'A':
        if (*name == '1')
            return 1;
        if (*name == '2')
            return 4;
        if (*name == '3')
            return 6;
        return 0;
    case 'B':
        if (*name == '1')
            return 2;
        if (*name == '2')
            return 5;
        return 0;
    case 'C':
        if (*name == '1')
            return 3;
        return 0;
    case 'N':
        if (*name == '1')
            return 7;
        if (*name == '2')
            return 8;
        return 0;
    case 'S':
        if (*name == '1')
            return 9;
        if (*name == '2')
            return 10;
    }
    return 0;
}

static bool json_litex_expr_alpha_builtin_func(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result);

// stev: the 'alpha' builtins:
//   [a-z]   function: num()
//   [A-Z]   function: bool()
//   [ABC]1  function: bool(num)
//   [AB]2   function: bool(num, num)
//   A3      function: bool(num, num, num)
//   N[12]   constant: num
//   S[12]   constant: str

static const struct json_litex_expr_builtin_t*
    json_litex_expr_lookup_alpha_builtin(
        struct json_litex_expr_t* expr,
        const uchar_t* name, size_t len)
{
    // 0  1  ... 24 25 26 27 ... 50 51 52 53 54 55 56 57 58 59 60 61
    // a  b  ...  y  z  A  B  ... Y  Z A1 B1 C1 A2 B2 A3 N1 N2 S1 S2
    enum { b = 'z' - 'a' + 1, N = 2 * b + 10 };
    static struct json_litex_expr_builtin_t defs[N];
    struct json_litex_expr_builtin_t* r;
    size_t k, n;
    uchar_t* s;
    bool u;

    if (len < 1 || len > 2 || !ISALPHA(*name) ||
        (len == 2 && !ISDIGIT(name[1])))
        return NULL;

    u = ISUPPER(*name);

    if (len == 1) {
        k = u ? (*name - 'A') + b
              : (*name - 'a');
        n = 0;
    }
    else {
        size_t i =
            json_litex_expr_lookup_alpha_builtin_name(
                name, len);
        ASSERT(i <= 10);
        if (i == 0)
            return NULL;

        k = 2 * b + (i - 1);
        n = name[1] - '0';
    }

    r = ARRAY_NULL_ELEM_REF(defs, k);
    ASSERT(r != NULL);

    if (r->name == NULL) {
        s = JSON_LITEX_EXPR_ALLOCATE_ARRAY(
                uchar_t, len + 1);
        memcpy(s, name, len);
        s[len] = 0;

        if (k < 58) {
            r->type = json_litex_expr_builtin_func;
            r->u.func.val =
                json_litex_expr_alpha_builtin_func;
            r->u.func.is_bool = u;
            r->u.func.n_args = n;
        }
        else
        if (k < 60) {
            r->type = json_litex_expr_builtin_num;
            r->u.num.val = k == 58 ? 123 : 456;
        }
        else {
            r->type = json_litex_expr_builtin_str;
            r->u.str.val = (const uchar_t*)
                (k == 60 ? "foo" : "bar");
        }
        r->name = s;
    }

    return r;
}

#endif // JSON_LITEX_TEST_EXPR

static bool json_litex_expr_str_prefix(
    const char* p, const char* q)
{
    while (*p && *p == *q)
         ++ p, ++ q;
    return *p == 0;
}

#if 0
static bool json_litex_expr_str_equal(
    const char* p, const char* q)
{
    while (*p && *p == *q)
         ++ p, ++ q;
    return *p == *q;
}
#endif

#define prefix json_litex_expr_str_prefix
#define equal  json_litex_expr_str_equal

// $ . ~/lookup-gen/commands.sh
// $ json-expr-builtins() { ssed -nR 's/^\s*JSON_LITEX_EXPR_BUILTIN_DECL\((.*?)\)\s*;\s*$/\1 =\&json_litex_expr_\1_builtin/p' json-litex.h; }
// $ json-expr-builtins|gen-func -f json_litex_expr_lookup_def_builtin_name -Pf -e NULL -u|ssed -R '1s/^/static /;1s/(?<=\()/\n\t/;1s/\bresult_t\&/const struct json_litex_expr_builtin_t**/;s/(?=t =)/*/;s/result_t:://'

static bool json_litex_expr_lookup_def_builtin_name(
    const char* n, const struct json_litex_expr_builtin_t** t)
{
    // pattern: boolean|c_locale|date|full_iso|i(nt|so_8601)|l(en|ong_iso)|nu(ll|mber)|string|timestamp|uint
    switch (*n ++) {
    case 'b':
        if (prefix(n, "oolean")) {
            *t = &json_litex_expr_boolean_builtin;
            return true;
        }
        return false;
    case 'c':
        if (prefix(n, "_locale")) {
            *t = &json_litex_expr_c_locale_builtin;
            return true;
        }
        return false;
    case 'd':
        if (prefix(n, "ate")) {
            *t = &json_litex_expr_date_builtin;
            return true;
        }
        return false;
    case 'f':
        if (prefix(n, "ull_iso")) {
            *t = &json_litex_expr_full_iso_builtin;
            return true;
        }
        return false;
    case 'i':
        switch (*n ++) {
        case 'n':
            if ((*n == 0 || (*n ++ == 't' &&
                (*n == 0)))) {
                *t = &json_litex_expr_int_builtin;
                return true;
            }
            return false;
        case 's':
            if (prefix(n, "o_8601")) {
                *t = &json_litex_expr_iso_8601_builtin;
                return true;
            }
        }
        return false;
    case 'l':
        switch (*n ++) {
        case 'e':
            if ((*n == 0 || (*n ++ == 'n' &&
                (*n == 0)))) {
                *t = &json_litex_expr_len_builtin;
                return true;
            }
            return false;
        case 'o':
            if (prefix(n, "ng_iso")) {
                *t = &json_litex_expr_long_iso_builtin;
                return true;
            }
        }
        return false;
    case 'n':
        if (*n ++ == 'u') {
            switch (*n ++) {
            case 'l':
                if ((*n == 0 || (*n ++ == 'l' &&
                    (*n == 0)))) {
                    *t = &json_litex_expr_null_builtin;
                    return true;
                }
                return false;
            case 'm':
                if (prefix(n, "ber")) {
                    *t = &json_litex_expr_number_builtin;
                    return true;
                }
            }
        }
        return false;
    case 's':
        if (prefix(n, "tring")) {
            *t = &json_litex_expr_string_builtin;
            return true;
        }
        return false;
    case 't':
        if (prefix(n, "imestamp")) {
            *t = &json_litex_expr_timestamp_builtin;
            return true;
        }
        return false;
    case 'u':
        if (prefix(n, "int")) {
            *t = &json_litex_expr_uint_builtin;
            return true;
        }
    }
    return false;
}

#undef prefix
#undef equal

static const struct json_litex_expr_builtin_t*
    json_litex_expr_lookup_def_builtin(
        struct json_litex_expr_t* expr UNUSED,
        const uchar_t* name, size_t len)
{
    enum { N = json_litex_expr_max_builtin_name_len + 1 };
    const struct json_litex_expr_builtin_t* r;
    char s[N];

    if (len >= N)
        return NULL;

    // stev: make a NUL-terminated copy of 'name'
    memcpy(s, name, len);
    s[len] = 0;

    if (json_litex_expr_lookup_def_builtin_name(s, &r)) {
        ASSERT(strulen(r->name) >= len);
        ASSERT(!memcmp(r->name, name, len));
        return r;
    }
    return NULL;
}

static const struct json_litex_expr_builtin_t*
    json_litex_expr_get_val_builtin(
        struct json_litex_expr_t* expr,
        const struct json_text_pos_t* pos,
        const uchar_t* val, size_t len)
{
    const struct json_litex_expr_builtin_t* b;

#ifdef JSON_LITEX_TEST_EXPR
    if (!(b = expr->lookup(expr, val, len)))
#else
    if (!(b = json_litex_expr_lookup_def_builtin(
                expr, val, len)))
#endif // JSON_LITEX_TEST_EXPR
        return JSON_LITEX_EXPR_ERROR(
            unknown_builtin, *pos);

    if (!JSON_LITEX_EXPR_BUILTIN_IS_CONST(b, num) &&
        !JSON_LITEX_EXPR_BUILTIN_IS_CONST(b, str))
        return JSON_LITEX_EXPR_BUILTIN_ERROR(
            b, not_val, *pos);

    return b;
}

static const struct json_litex_expr_builtin_t*
    json_litex_expr_get_func_builtin(
        struct json_litex_expr_t* expr,
        const struct json_text_pos_t* pos,
        const uchar_t* val, size_t len,
        size_t n_args)
{
    const struct json_litex_expr_builtin_t* b;

#ifdef JSON_LITEX_TEST_EXPR
    if (!(b = expr->lookup(expr, val, len)))
#else
    if (!(b = json_litex_expr_lookup_def_builtin(
                expr, val, len)))
#endif // JSON_LITEX_TEST_EXPR
        return JSON_LITEX_EXPR_ERROR(
            unknown_builtin, *pos);

    if (!JSON_LITEX_EXPR_BUILTIN_IS_CONST(b, func))
        return JSON_LITEX_EXPR_BUILTIN_ERROR(
            b, not_func, *pos);

    if (b->u.func.n_args != n_args)
        return JSON_LITEX_EXPR_BUILTIN_FUNC_ERROR(
            b, n_args, *pos, n_args);

    return b;
}

#undef  STACK_NAME
#define STACK_NAME      json_litex_expr_flat
#undef  STACK_ELEM_TYPE
#define STACK_ELEM_TYPE struct json_litex_expr_flat_attr_t

#define STACK_NEED_MAX_SIZE
#define STACK_NEED_IMPL_ONLY
#include "stack-impl.h"
#undef  STACK_NEED_IMPL_ONLY
#undef  STACK_NEED_MAX_SIZE

#define JSON_LITEX_EXPR_FLAT_STACK_OP(o, ...) \
    STACK_ ## o(&impl->stack, ## __VA_ARGS__)

#define JSON_LITEX_EXPR_FLAT_STACK_PUSH(b, f, t)   \
    ({                                             \
        struct json_litex_expr_flat_attr_t __a = { \
            .is_bool = b,                          \
            .false_list = f,                       \
            .true_list = t,                        \
            .jump = 0,                             \
            .pos = *pos                            \
        };                                         \
        JSON_LITEX_EXPR_FLAT_STACK_OP(PUSH, __a);  \
    })

#define JSON_LITEX_EXPR_FLAT_STACK_SIZE() \
        JSON_LITEX_EXPR_FLAT_STACK_OP(SIZE)
#define JSON_LITEX_EXPR_FLAT_STACK_POP() \
        JSON_LITEX_EXPR_FLAT_STACK_OP(POP)
#define JSON_LITEX_EXPR_FLAT_STACK_POP_N(n) \
        JSON_LITEX_EXPR_FLAT_STACK_OP(POP_N, SZ(n))
#define JSON_LITEX_EXPR_FLAT_STACK_TOP_REF() \
        JSON_LITEX_EXPR_FLAT_STACK_OP(TOP_REF)
#define JSON_LITEX_EXPR_FLAT_STACK_TOP_N_REF(n) \
        JSON_LITEX_EXPR_FLAT_STACK_OP(TOP_N_REF, n)

static inline void json_litex_expr_flat_stack_clear(
    struct json_litex_expr_flat_stack_t *stack)
{
    STACK_CLEAR(stack);
}

#define JSON_LITEX_EXPR_IMPL_IS(n) \
    (expr->impl.type == json_litex_expr_ ## n ## _impl_type)
#define JSON_LITEX_EXPR_IMPL_AS(n)          \
    ({                                      \
        ASSERT(JSON_LITEX_EXPR_IMPL_IS(n)); \
        &(expr->impl.n);                    \
    })
#define JSON_LITEX_EXPR_IMPL_AS_IF(n) \
    (                                 \
        JSON_LITEX_EXPR_IMPL_IS(n)    \
        ? &(expr->impl.n) : NULL      \
    )
#define JSON_LITEX_EXPR_IMPL_PTR()                       \
    ({                                                   \
        void* __r;                                       \
        if (!(__r = JSON_LITEX_EXPR_IMPL_AS_IF(flat)) && \
            !(__r = JSON_LITEX_EXPR_IMPL_AS_IF(ast)))    \
            UNEXPECT_VAR("%d", expr->impl.type);         \
        __r;                                             \
    })

static void json_litex_expr_flat_impl_init(
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_litex_expr_flat_sizes_t* sizes)
{
    json_litex_expr_flat_stack_init(
        &impl->stack,
        sizes->stack_max,
        sizes->stack_init);
}

static void json_litex_expr_flat_impl_done(
    struct json_litex_expr_flat_impl_t* impl)
{
    json_litex_expr_flat_stack_done(
        &impl->stack);
}

static void json_litex_expr_flat_impl_clear(
    struct json_litex_expr_flat_impl_t* impl)
{
    json_litex_expr_flat_stack_clear(
        &impl->stack);
}

static bool json_litex_expr_flat_impl_const_id(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len)
{
    const struct json_litex_expr_builtin_t* d;
    struct json_litex_expr_node_t* n;

    if (!(d = json_litex_expr_get_val_builtin(
                expr, pos, val, len)))
        return false;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_const_id;
    n->node.const_id = d;

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        false, 0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_const_num(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    json_litex_expr_value_t val)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_const_num;
    n->node.const_num = val;

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        val == 0 || val == 1,
        0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_const_str(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_const_str;
    n->node.const_str = json_litex_expr_new_str(
        expr, val, len);

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        false, 0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_count_str(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_count_str;
    n->node.count_str = json_litex_expr_new_str(
        expr, val, len);

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        false, 0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_count_rex(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len,
    size_t opts, fnv_hash_t hash)
{
    struct json_litex_expr_node_t* n;
    json_litex_expr_rex_index_t r;

    if (!json_litex_expr_new_rex(
            expr, pos, val, len, opts, hash, &r))
        return false;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_count_rex;
    n->node.count_rex = r;

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        false, 0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_match_str(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_match_str;
    n->node.match_str = json_litex_expr_new_str(
        expr, val, len);

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        true, 0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_match_rex(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len,
    size_t opts, fnv_hash_t hash)
{
    struct json_litex_expr_node_t* n;
    json_litex_expr_rex_index_t r;

    if (!json_litex_expr_new_rex(
            expr, pos, val, len, opts, hash, &r))
        return false;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_match_rex;
    n->node.match_rex = r;

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        true, 0, 0);

    return true;
}

#undef  CASE
#define CASE(n) case json_litex_expr_node_ ## n

static bool json_litex_expr_flat_impl_call_builtin(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len,
    size_t n_args)
{
    const struct json_litex_expr_builtin_func_t* f;
    const struct json_litex_expr_builtin_t* b;
    struct json_litex_expr_node_t *n;

    if (!(b = json_litex_expr_get_func_builtin(
                expr, pos, val, len, n_args)))
        return false;

    f = JSON_LITEX_EXPR_BUILTIN_AS_CONST(b, func);
    ASSERT(f->n_args == n_args);

    if (n_args > 0) {
        const struct json_litex_expr_node_t *p, *e;
        size_t n = VECTOR_N_ELEMS(&expr->nodes), k = 0;
        ASSERT(n >= n_args); // => n > 0

        for (p = VECTOR_ELEM_REF(
                    &expr->nodes, n - n_args),
             e = VECTOR_ELEM_REF(
                    &expr->nodes, n - 1);
             p <= e;
             p ++,
             k ++) {
            ASSERT(k <= n_args - 1);
            // 0 <= k <= n_args - 1 <=>
            // 0 <= (n_args - 1) - k <= n_args - 1

            switch (p->type) {

            CASE(const_id):
                if (JSON_LITEX_EXPR_BUILTIN_IS_CONST(
                        p->node.const_id, num))
                    goto const_num;
                else
                if (JSON_LITEX_EXPR_BUILTIN_IS_CONST(
                        p->node.const_id, str))
                    goto const_str;
                else
                if (JSON_LITEX_EXPR_BUILTIN_IS_CONST(
                        p->node.const_id, func))
                    ASSERT(false);
                else
                    UNEXPECT_VAR("%d",
                        p->node.const_id->type);
                break;

                 const_num:
            CASE(const_num):
                if (!JSON_LITEX_EXPR_BUILTIN_FUNC_ARG_IS(
                        f->args, num, k)) {
                    struct json_litex_expr_flat_attr_t* a =
                        JSON_LITEX_EXPR_FLAT_STACK_TOP_N_REF(
                            (n_args - 1) - k);
                    return JSON_LITEX_EXPR_BUILTIN_FUNC_ERROR(
                        b, arg_type, a->pos, k);
                }
                break;

                 const_str:
            CASE(const_str):
                if (!JSON_LITEX_EXPR_BUILTIN_FUNC_ARG_IS(
                        f->args, str, k)) {
                    struct json_litex_expr_flat_attr_t* a =
                        JSON_LITEX_EXPR_FLAT_STACK_TOP_N_REF(
                            (n_args - 1) - k);
                    return JSON_LITEX_EXPR_BUILTIN_FUNC_ERROR(
                        b, arg_type, a->pos, k);
                }
                break;

            CASE(count_str):
            CASE(count_rex):
            CASE(match_str):
            CASE(match_rex):
            CASE(call_builtin):
            CASE(jump_false):
            CASE(jump_true):
            CASE(make_bool):
            CASE(cmp_op):
            CASE(not_op):
                ASSERT(false);
                break;

            default:
                UNEXPECT_VAR("%d", p->type);
            }
        }

        JSON_LITEX_EXPR_FLAT_STACK_POP_N(n_args - 1);
    }

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_call_builtin;
    n->node.call_builtin = b;

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        f->is_bool, 0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_cmp_in(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos UNUSED)
{
    const struct json_litex_expr_node_t* n;
    struct json_litex_expr_flat_attr_t* t;

    t = JSON_LITEX_EXPR_FLAT_STACK_TOP_REF();

    json_litex_expr_backpatch_flat_attr(expr, t);

    n = VECTOR_BACK_ELEM_REF(&expr->nodes);

    t->ins =
        JSON_LITEX_EXPR_NODE_IS_CONST(
            n, count_str) ||
        JSON_LITEX_EXPR_NODE_IS_CONST(
            n, count_rex) ||
        JSON_LITEX_EXPR_NODE_IS_CONST(
            n, match_str) ||
        JSON_LITEX_EXPR_NODE_IS_CONST(
            n, match_rex)
        ? expr->nodes.n_elems - 1
        : SIZE_MAX;

    return true;
}

#undef  CASE
#define CASE(n, r)                   \
    [json_litex_expr_cmp_op_ ## n] = \
     json_litex_expr_cmp_op_ ## r
static const enum json_litex_expr_cmp_op_t
    json_litex_expr_cmp_op_reverses[] = {
        CASE(eq, eq),
        CASE(ne, ne),
        CASE(lt, gt),
        CASE(le, ge),
        CASE(gt, lt),
        CASE(ge, le),
    };

#define JSON_LITEX_EXPR_CMP_OP_REVERSE(o)   \
    ({                                      \
        STATIC(TYPEOF_IS_UNSIGNED_INT(o));  \
        ASSERT(o < ARRAY_SIZE(              \
        json_litex_expr_cmp_op_reverses));  \
        json_litex_expr_cmp_op_reverses[o]; \
    })

#undef  JSON_LITEX_EXPR_FLAT_STACK_TOP_N_REF
#define JSON_LITEX_EXPR_FLAT_STACK_TOP_N_REF(n) \
        JSON_LITEX_EXPR_FLAT_STACK_OP(TOP_N_REF, SZ(n))

static bool json_litex_expr_flat_impl_cmp_op(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos,
    enum json_litex_expr_cmp_op_t val)
{
    struct json_litex_expr_flat_attr_t *t0, *t1;
    struct json_litex_expr_node_t* n;

    t0 = JSON_LITEX_EXPR_FLAT_STACK_TOP_N_REF(0);
    t1 = JSON_LITEX_EXPR_FLAT_STACK_TOP_N_REF(1);

    json_litex_expr_backpatch_flat_attr(expr, t0);

    if (t1->ins < SIZE_MAX) {
        size_t n = VECTOR_N_ELEMS(&expr->nodes);

        ASSERT(n >= 2);
        ASSERT(n - 1 > t1->ins);
        if (n - t1->ins == 2) {
            const struct json_litex_expr_node_t* p =
                VECTOR_ELEM_REF(&expr->nodes, n - 1);
            const struct json_litex_expr_node_t* q =
                VECTOR_ELEM_REF(&expr->nodes, n - 2);

            if (JSON_LITEX_EXPR_NODE_IS_CONST(
                    p, count_str) ||
                JSON_LITEX_EXPR_NODE_IS_CONST(
                    p, count_rex) ||
                p->type == q->type)
                t1->ins = SIZE_MAX;
        }
    }

    // stev: need not offset the jump locations;
    // should the jump instructions been defined
    // to contain absolute offsets, offseting by
    // -1 each such jump ops would be obligatory
    // -- making the code slightly slower!
    if (t1->ins < SIZE_MAX) {
        json_litex_expr_nodes_vector_rotate_elem(
            &expr->nodes, t1->ins);
        val = JSON_LITEX_EXPR_CMP_OP_REVERSE(val);
    }

    JSON_LITEX_EXPR_FLAT_STACK_POP_N(1);

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_cmp_op;
    n->node.cmp_op = val;

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        true, 0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_not_op(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos)
{
    struct json_litex_expr_flat_attr_t t;
    struct json_litex_expr_node_t* n;

    t = JSON_LITEX_EXPR_FLAT_STACK_POP();

    json_litex_expr_backpatch_flat_attr(
        expr, &t);

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_not_op;

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        true, 0, 0);

    return true;
}

static bool json_litex_expr_flat_impl_and_in(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos UNUSED)
{
    struct json_litex_expr_flat_attr_t* t;
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_jump_false;

    t = JSON_LITEX_EXPR_FLAT_STACK_TOP_REF();
    t->jump = JSON_LITEX_EXPR_LAST_AS_LIST();

    json_litex_expr_backpatch_jump_list(
        expr, t->true_list,
        true);

    return true;
}

static bool json_litex_expr_flat_impl_and_op(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos)
{
    struct json_litex_expr_flat_attr_t t0, t1;
    struct json_litex_expr_node_t* n;

    t1 = JSON_LITEX_EXPR_FLAT_STACK_POP();
    t0 = JSON_LITEX_EXPR_FLAT_STACK_POP();

    ASSERT(t0.jump > 0);

    json_litex_expr_merge_jump_lists(
        expr, &t0.false_list,
        t1.false_list);
    json_litex_expr_merge_jump_lists(
        expr, &t0.false_list,
        t0.jump);

    if (!t1.is_bool) {
        n = json_litex_expr_new_node(expr);
        n->type = json_litex_expr_node_make_bool;
    }

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        true, t0.false_list, t1.true_list);

    return true;
}

static bool json_litex_expr_flat_impl_or_in(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos UNUSED)
{
    struct json_litex_expr_flat_attr_t* t;
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_jump_true;

    t = JSON_LITEX_EXPR_FLAT_STACK_TOP_REF();
    t->jump = JSON_LITEX_EXPR_LAST_AS_LIST();

    json_litex_expr_backpatch_jump_list(
        expr, t->false_list,
        true);

    return true;
}

static bool json_litex_expr_flat_impl_or_op(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl,
    const struct json_text_pos_t* pos)
{
    struct json_litex_expr_flat_attr_t t0, t1;
    struct json_litex_expr_node_t* n;

    t1 = JSON_LITEX_EXPR_FLAT_STACK_POP();
    t0 = JSON_LITEX_EXPR_FLAT_STACK_POP();

    ASSERT(t0.jump > 0);

    json_litex_expr_merge_jump_lists(
        expr, &t0.true_list,
        t1.true_list);
    json_litex_expr_merge_jump_lists(
        expr, &t0.true_list,
        t0.jump);

    if (!t1.is_bool) {
        n = json_litex_expr_new_node(expr);
        n->type = json_litex_expr_node_make_bool;
    }

    JSON_LITEX_EXPR_FLAT_STACK_PUSH(
        true, t1.false_list, t0.true_list);

    return true;
}

static bool json_litex_expr_flat_impl_top(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_flat_impl_t* impl)
{
    struct json_litex_expr_flat_attr_t t;
    size_t s;

    s = JSON_LITEX_EXPR_FLAT_STACK_SIZE();
    ENSURE(s == 1, "invalid expression stack: "
        "size is %zu", s);

    t = JSON_LITEX_EXPR_FLAT_STACK_POP();

    json_litex_expr_backpatch_flat_attr(
        expr, &t);

    return true;
}

#define JSON_LITEX_AST_VISITOR_NAME \
    json_litex_expr_ast_gen_code
#define JSON_LITEX_AST_VISITOR_RESULT_TYPE \
    struct json_litex_expr_ast_attr_t
#define JSON_LITEX_AST_VISITOR_OBJECT_TYPE \
    struct json_litex_expr_t

#include "json-litex-ast-impl.h"

#define JSON_LITEX_EXPR_AST_ATTR(b, f, t) \
    (struct json_litex_expr_ast_attr_t) { \
        .is_bool = b,                     \
        .false_list = f,                  \
        .true_list = t                    \
    }

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_const_id(
        struct json_litex_expr_t* expr,
        const struct json_litex_expr_builtin_t* id)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_const_id;
    n->node.const_id = id;

    return JSON_LITEX_EXPR_AST_ATTR(
        false, 0, 0);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_const_num(
        struct json_litex_expr_t* expr,
        const json_litex_expr_value_t num)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_const_num;
    n->node.const_num = num;

    return JSON_LITEX_EXPR_AST_ATTR(
        num == 0 || num == 1,
        0, 0);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_const_str(
        struct json_litex_expr_t* expr,
        const json_litex_expr_string_t str)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_const_str;
    n->node.const_str = str;

    return JSON_LITEX_EXPR_AST_ATTR(
        false, 0, 0);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_count_str(
        struct json_litex_expr_t* expr,
        const json_litex_expr_string_t str)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_count_str;
    n->node.count_str = str;

    return JSON_LITEX_EXPR_AST_ATTR(
        false, 0, 0);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_count_rex(
        struct json_litex_expr_t* expr,
        json_litex_expr_rex_index_t rex)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_count_rex;
    n->node.count_rex = rex;

    return JSON_LITEX_EXPR_AST_ATTR(
        false, 0, 0);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_match_str(
        struct json_litex_expr_t* expr,
        const json_litex_expr_string_t str)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_match_str;
    n->node.match_str = str;

    return JSON_LITEX_EXPR_AST_ATTR(
        true, 0, 0);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_match_rex(
        struct json_litex_expr_t* expr,
        json_litex_expr_rex_index_t rex)
{
    struct json_litex_expr_node_t* n;

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_match_rex;
    n->node.match_rex = rex;

    return JSON_LITEX_EXPR_AST_ATTR(
        true, 0, 0);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_call_builtin(
        struct json_litex_expr_t* expr,
        struct json_litex_expr_ast_call_builtin_node_t* call)
{
    const struct json_litex_expr_builtin_func_t* f;
    struct json_litex_expr_node_t* n;

    f = JSON_LITEX_EXPR_BUILTIN_AS_CONST(
            call->builtin, func);

    if (f->n_args > 0) {
        struct json_litex_expr_ast_node_t **p, **e;

        for (p = call->args,
             e = p + f->n_args;
             p < e;
             p ++)
            json_litex_expr_ast_gen_code_visit(
                expr, *p);
    }

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_call_builtin;
    n->node.call_builtin = call->builtin;

    return JSON_LITEX_EXPR_AST_ATTR(
        f->is_bool, 0, 0);
}

#define STATIC_AST_CMP_OP_EQ_EXPR_CMP_OP() \
    STATIC(                                \
        json_litex_expr_ast_bin_op_eq ==   \
        0 + json_litex_expr_cmp_op_eq &&   \
        json_litex_expr_ast_bin_op_ne ==   \
        0 + json_litex_expr_cmp_op_ne &&   \
        json_litex_expr_ast_bin_op_lt ==   \
        0 + json_litex_expr_cmp_op_lt &&   \
        json_litex_expr_ast_bin_op_le ==   \
        0 + json_litex_expr_cmp_op_le &&   \
        json_litex_expr_ast_bin_op_gt ==   \
        0 + json_litex_expr_cmp_op_gt &&   \
        json_litex_expr_ast_bin_op_ge ==   \
        0 + json_litex_expr_cmp_op_ge)

#define BIN_OP(n) \
    json_litex_expr_ast_bin_op_ ## n

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_cmp_op(
        struct json_litex_expr_t* expr,
        struct json_litex_expr_ast_binary_node_t* bin)
{
    struct json_litex_expr_ast_node_t* t;
    struct json_litex_expr_ast_attr_t a;
    struct json_litex_expr_node_t* n;

    STATIC(
        BIN_OP(eq) == 0 &&
        BIN_OP(eq) + 1 == BIN_OP(ne) &&
        BIN_OP(ne) + 1 == BIN_OP(lt) &&
        BIN_OP(lt) + 1 == BIN_OP(le) &&
        BIN_OP(le) + 1 == BIN_OP(gt) &&
        BIN_OP(gt) + 1 == BIN_OP(ge));

    STATIC(TYPEOF_IS_UNSIGNED_INT(bin->op));

    STATIC_AST_CMP_OP_EQ_EXPR_CMP_OP();

    ASSERT(bin->op <= BIN_OP(ge));

    if ((bin->left->type !=
         bin->right->type) &&
        (JSON_LITEX_EXPR_AST_NODE_IS(
            bin->left, count_str) ||
         JSON_LITEX_EXPR_AST_NODE_IS(
            bin->left, count_rex) ||
         JSON_LITEX_EXPR_AST_NODE_IS(
            bin->left, match_str) ||
         JSON_LITEX_EXPR_AST_NODE_IS(
            bin->left, match_rex)) &&
        !JSON_LITEX_EXPR_AST_NODE_IS(
            bin->right, count_str) &&
        !JSON_LITEX_EXPR_AST_NODE_IS(
            bin->right, count_rex)) {
        t = bin->right;
        bin->right = bin->left;
        bin->left = t;
        bin->op =
            JSON_LITEX_EXPR_CMP_OP_REVERSE(
                bin->op);
    }

    a = json_litex_expr_ast_gen_code_visit(
            expr, bin->left);
    json_litex_expr_backpatch_ast_attr(
            expr, &a);

    a = json_litex_expr_ast_gen_code_visit(
            expr, bin->right);
    json_litex_expr_backpatch_ast_attr(
            expr, &a);

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_cmp_op;
    n->node.cmp_op = bin->op;

    return JSON_LITEX_EXPR_AST_ATTR(
        true, 0, 0);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_and_op(
        struct json_litex_expr_t* expr,
        struct json_litex_expr_ast_binary_node_t* bin)
{
    struct json_litex_expr_ast_attr_t a, b;
    struct json_litex_expr_node_t* n;
    json_litex_expr_jump_list_t j;

    ASSERT(bin->op == BIN_OP(and));

    a = json_litex_expr_ast_gen_code_visit(
            expr, bin->left);

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_jump_false;

    j = JSON_LITEX_EXPR_LAST_AS_LIST();

    json_litex_expr_backpatch_jump_list(
        expr, a.true_list,
        true);

    b = json_litex_expr_ast_gen_code_visit(
            expr, bin->right);

    json_litex_expr_merge_jump_lists(
        expr, &a.false_list,
        b.false_list);
    json_litex_expr_merge_jump_lists(
        expr, &a.false_list,
        j);

    if (!b.is_bool) {
        n = json_litex_expr_new_node(expr);
        n->type = json_litex_expr_node_make_bool;
    }

    return JSON_LITEX_EXPR_AST_ATTR(
        true, a.false_list, b.true_list);
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_or_op(
        struct json_litex_expr_t* expr,
        struct json_litex_expr_ast_binary_node_t* bin)
{
    struct json_litex_expr_ast_attr_t a, b;
    struct json_litex_expr_node_t* n;
    json_litex_expr_jump_list_t j;

    ASSERT(bin->op == BIN_OP(or));

    a = json_litex_expr_ast_gen_code_visit(
            expr, bin->left);

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_jump_true;

    j = JSON_LITEX_EXPR_LAST_AS_LIST();

    json_litex_expr_backpatch_jump_list(
        expr, a.false_list,
        true);

    b = json_litex_expr_ast_gen_code_visit(
            expr, bin->right);

    json_litex_expr_merge_jump_lists(
        expr, &a.true_list,
        b.true_list);
    json_litex_expr_merge_jump_lists(
        expr, &a.true_list,
        j);

    if (!b.is_bool) {
        n = json_litex_expr_new_node(expr);
        n->type = json_litex_expr_node_make_bool;
    }

    return JSON_LITEX_EXPR_AST_ATTR(
        true, b.false_list, a.true_list);
}

#undef BIN_OP

#undef  CASE
#define CASE(n) \
    case json_litex_expr_ast_bin_op_ ## n

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_bin_op(
        struct json_litex_expr_t* expr,
        struct json_litex_expr_ast_binary_node_t* bin)
{
    switch (bin->op) {

    CASE(eq):
    CASE(ne):
    CASE(lt):
    CASE(le):
    CASE(gt):
    CASE(ge):
        return json_litex_expr_ast_gen_code_cmp_op(
            expr, bin);
    CASE(and):
        return json_litex_expr_ast_gen_code_and_op(
            expr, bin);
    CASE(or):
        return json_litex_expr_ast_gen_code_or_op(
            expr, bin);

    default:
        UNEXPECT_VAR("%d", bin->op);
    }
}

static struct json_litex_expr_ast_attr_t
    json_litex_expr_ast_gen_code_not_op(
        struct json_litex_expr_t* expr,
        struct json_litex_expr_ast_unary_node_t* not)
{
    struct json_litex_expr_ast_attr_t a;
    struct json_litex_expr_node_t* n;

    a = json_litex_expr_ast_gen_code_visit(
            expr, not->node);
    json_litex_expr_backpatch_ast_attr(
        expr, &a);

    n = json_litex_expr_new_node(expr);
    n->type = json_litex_expr_node_not_op;

    return JSON_LITEX_EXPR_AST_ATTR(
        true, 0, 0);
}

#undef  STACK_NAME
#define STACK_NAME      json_litex_expr_ast
#undef  STACK_ELEM_TYPE
#define STACK_ELEM_TYPE struct json_litex_expr_ast_node_t*

#define STACK_NEED_MAX_SIZE
#define STACK_NEED_IMPL_ONLY
#include "stack-impl.h"
#undef  STACK_NEED_IMPL_ONLY
#undef  STACK_NEED_MAX_SIZE

#define JSON_LITEX_EXPR_AST_STACK_OP(o, ...) \
    STACK_ ## o(&impl->stack, ## __VA_ARGS__)

#define JSON_LITEX_EXPR_AST_STACK_PUSH(n) \
        JSON_LITEX_EXPR_AST_STACK_OP(PUSH, n)
#define JSON_LITEX_EXPR_AST_STACK_SIZE() \
        JSON_LITEX_EXPR_AST_STACK_OP(SIZE)
#define JSON_LITEX_EXPR_AST_STACK_POP() \
        JSON_LITEX_EXPR_AST_STACK_OP(POP)
#define JSON_LITEX_EXPR_AST_STACK_POP_N(n) \
        JSON_LITEX_EXPR_AST_STACK_OP(POP_N, SZ(n))
#define JSON_LITEX_EXPR_AST_STACK_TOP() \
        JSON_LITEX_EXPR_AST_STACK_OP(TOP)
#define JSON_LITEX_EXPR_AST_STACK_TOP_N(n) \
        JSON_LITEX_EXPR_AST_STACK_OP(TOP_N, n)
#define JSON_LITEX_EXPR_AST_STACK_TOP_REF() \
        JSON_LITEX_EXPR_AST_STACK_OP(TOP_REF)

static inline void json_litex_expr_ast_stack_clear(
    struct json_litex_expr_ast_stack_t *stack)
{
    STACK_CLEAR(stack);
}

static void json_litex_expr_ast_impl_init(
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_litex_expr_ast_sizes_t* sizes)
{
    pool_alloc_init(
        &impl->pool,
        sizes->pool_size);
    json_litex_expr_ast_stack_init(
        &impl->stack,
        sizes->stack_max,
        sizes->stack_init);
}

static void json_litex_expr_ast_impl_done(
    struct json_litex_expr_ast_impl_t* impl)
{
    json_litex_expr_ast_stack_done(
        &impl->stack);
    pool_alloc_done(&impl->pool);
}

static void json_litex_expr_ast_impl_clear(
    struct json_litex_expr_ast_impl_t* impl)
{
    json_litex_expr_ast_stack_clear(
        &impl->stack);
    pool_alloc_clear(&impl->pool);
}

static void* json_litex_expr_ast_impl_allocate(
    struct json_litex_expr_ast_impl_t* impl,
    size_t size, size_t align)
{
    void* n;

    if (!(n = pool_alloc_allocate(
            &impl->pool, size, align)))
        fatal_error("ast-impl pool alloc failed");

    memset(n, 0, size);

    return n;
}

#define JSON_LITEX_EXPR_AST_IMPL_ALLOCATE_STRUCT(t) \
    (                                               \
        json_litex_expr_ast_impl_allocate(impl,     \
            sizeof(struct t),                       \
            MEM_ALIGNOF(struct t))                  \
    )

#define JSON_LITEX_EXPR_AST_IMPL_ALLOCATE_ARRAY(t, n) \
    ({                                                \
        STATIC(TYPEOF_IS_SIZET(n));                   \
        json_litex_expr_ast_impl_allocate(impl,       \
            SIZE_MUL(n, sizeof(t)),                   \
            MEM_ALIGNOF(t[0]));                       \
    })

static struct json_litex_expr_ast_node_t*
    json_litex_expr_ast_impl_new_node(
        struct json_litex_expr_ast_impl_t* impl)
{
    return JSON_LITEX_EXPR_AST_IMPL_ALLOCATE_STRUCT(
        json_litex_expr_ast_node_t);
}

static bool json_litex_expr_ast_impl_const_id(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len)
{
    const struct json_litex_expr_builtin_t* d;
    struct json_litex_expr_ast_node_t* n;

    if (!(d = json_litex_expr_get_val_builtin(
                expr, pos, val, len)))
        return false;

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_const_id;
    n->const_id = d;
    n->pos = *pos;

    JSON_LITEX_EXPR_AST_STACK_PUSH(n);

    return true;
}

static bool json_litex_expr_ast_impl_const_num(
    struct json_litex_expr_t* expr UNUSED,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    json_litex_expr_value_t val)
{
    struct json_litex_expr_ast_node_t* n;

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_const_num;
    n->const_num = val;
    n->pos = *pos;

    JSON_LITEX_EXPR_AST_STACK_PUSH(n);

    return true;
}

static bool json_litex_expr_ast_impl_const_str(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len)
{
    struct json_litex_expr_ast_node_t* n;

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_const_str;
    n->const_str = json_litex_expr_new_str(
        expr, val, len);
    n->pos = *pos;

    JSON_LITEX_EXPR_AST_STACK_PUSH(n);

    return true;
}

static bool json_litex_expr_ast_impl_count_str(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len)
{
    struct json_litex_expr_ast_node_t* n;

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_count_str;
    n->count_str = json_litex_expr_new_str(
        expr, val, len);
    n->pos = *pos;

    JSON_LITEX_EXPR_AST_STACK_PUSH(n);

    return true;
}

static bool json_litex_expr_ast_impl_count_rex(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len,
    size_t opts, fnv_hash_t hash)
{
    struct json_litex_expr_ast_node_t* n;
    json_litex_expr_rex_index_t r;

    if (!json_litex_expr_new_rex(
            expr, pos, val, len, opts, hash, &r))
        return false;

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_count_rex;
    n->count_rex = r;

    JSON_LITEX_EXPR_AST_STACK_PUSH(n);

    return true;
}

static bool json_litex_expr_ast_impl_match_str(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len)
{
    struct json_litex_expr_ast_node_t* n;

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_match_str;
    n->const_str = json_litex_expr_new_str(
        expr, val, len);
    n->pos = *pos;

    JSON_LITEX_EXPR_AST_STACK_PUSH(n);

    return true;
}

static bool json_litex_expr_ast_impl_match_rex(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len,
    size_t opts, fnv_hash_t hash)
{
    struct json_litex_expr_ast_node_t* n;
    json_litex_expr_rex_index_t r;

    if (!json_litex_expr_new_rex(
            expr, pos, val, len, opts, hash, &r))
        return false;

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_match_rex;
    n->match_rex = r;

    JSON_LITEX_EXPR_AST_STACK_PUSH(n);

    return true;
}

#undef  CASE
#define CASE(n) case json_litex_expr_ast_node_ ## n

static bool json_litex_expr_ast_impl_call_builtin(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    const uchar_t* val, size_t len,
    size_t n_args)
{
    const struct json_litex_expr_builtin_func_t* f;
    const struct json_litex_expr_builtin_t* b;
    struct json_litex_expr_ast_node_t *n, **a;

    if (!(b = json_litex_expr_get_func_builtin(
                expr, pos, val, len, n_args)))
        return false;

    f = JSON_LITEX_EXPR_BUILTIN_AS_CONST(b, func);
    ASSERT(f->n_args == n_args);

    if (n_args == 0)
        a = NULL;
    else {
        struct json_litex_expr_ast_node_t *p, *e, **q;
        size_t k = 0;

        a = JSON_LITEX_EXPR_AST_IMPL_ALLOCATE_ARRAY(
            struct json_litex_expr_ast_node_t*,
            n_args);

        for (p = JSON_LITEX_EXPR_AST_STACK_TOP_N(
                    n_args - 1),
             e = JSON_LITEX_EXPR_AST_STACK_TOP(),
             q = a;
             p <= e;
             p ++,
             k ++) {
            ASSERT(k <= n_args - 1);
            // 0 <= k <= n_args - 1 <=>
            // 0 <= (n_args - 1) - k <= n_args - 1

            switch (p->type) {

            CASE(const_id):
                if (JSON_LITEX_EXPR_BUILTIN_IS_CONST(
                        p->const_id, num))
                    goto const_num;
                else
                if (JSON_LITEX_EXPR_BUILTIN_IS_CONST(
                        p->const_id, str))
                    goto const_str;
                else
                if (JSON_LITEX_EXPR_BUILTIN_IS_CONST(
                        p->const_id, func))
                    ASSERT(false);
                else
                    UNEXPECT_VAR("%d",
                        p->const_id->type);
                break;

                 const_num:
            CASE(const_num):
                if (!JSON_LITEX_EXPR_BUILTIN_FUNC_ARG_IS(
                        f->args, num, k))
                    return JSON_LITEX_EXPR_BUILTIN_FUNC_ERROR(
                        b, arg_type, p->pos, k);
                break;

                 const_str:
            CASE(const_str):
                if (!JSON_LITEX_EXPR_BUILTIN_FUNC_ARG_IS(
                        f->args, str, k))
                    return JSON_LITEX_EXPR_BUILTIN_FUNC_ERROR(
                        b, arg_type, p->pos, k);
                break;

            CASE(count_str):
            CASE(count_rex):
            CASE(match_str):
            CASE(match_rex):
            CASE(call_builtin):
            CASE(bin_op):
            CASE(not_op):
                ASSERT(false);
                break;

            default:
                UNEXPECT_VAR("%d", p->type);
            }

            *q ++ = p;
        }

        JSON_LITEX_EXPR_AST_STACK_POP_N(n_args - 1);
    }

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_call_builtin;
    n->call_builtin.builtin = b;
    n->call_builtin.args = a;
    n->pos = *pos;

    JSON_LITEX_EXPR_AST_STACK_PUSH(n);

    return true;
}

static bool json_litex_expr_ast_impl_cmp_op(
    struct json_litex_expr_t* expr UNUSED,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos,
    enum json_litex_expr_cmp_op_t val)
{
    struct json_litex_expr_ast_node_t **l, *r, *n;

    STATIC_AST_CMP_OP_EQ_EXPR_CMP_OP();

    r = JSON_LITEX_EXPR_AST_STACK_POP();
    l = JSON_LITEX_EXPR_AST_STACK_TOP_REF();

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_bin_op;
    n->bin_op.right = r;
    n->bin_op.left = *l;
    n->bin_op.op = val;
    n->pos = *pos;

    *l = n;

    return true;
}

static bool json_litex_expr_ast_impl_not_op(
    struct json_litex_expr_t* expr UNUSED,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos)
{
    struct json_litex_expr_ast_node_t **t, *n;

    t = JSON_LITEX_EXPR_AST_STACK_TOP_REF();

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_not_op;
    n->not_op.node = *t;
    n->pos = *pos;

    *t = n;

    return true;
}

static bool json_litex_expr_ast_impl_and_op(
    struct json_litex_expr_t* expr UNUSED,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos)
{
    struct json_litex_expr_ast_node_t **l, *r, *n;

    r = JSON_LITEX_EXPR_AST_STACK_POP();
    l = JSON_LITEX_EXPR_AST_STACK_TOP_REF();

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_bin_op;
    n->bin_op.op = json_litex_expr_ast_bin_op_and;
    n->bin_op.right = r;
    n->bin_op.left = *l;
    n->pos = *pos;

    *l = n;

    return true;
}

static bool json_litex_expr_ast_impl_or_op(
    struct json_litex_expr_t* expr UNUSED,
    struct json_litex_expr_ast_impl_t* impl,
    const struct json_text_pos_t* pos)
{
    struct json_litex_expr_ast_node_t **l, *r, *n;

    r = JSON_LITEX_EXPR_AST_STACK_POP();
    l = JSON_LITEX_EXPR_AST_STACK_TOP_REF();

    n = json_litex_expr_ast_impl_new_node(impl);
    n->type = json_litex_expr_ast_node_bin_op;
    n->bin_op.op = json_litex_expr_ast_bin_op_or;
    n->bin_op.right = r;
    n->bin_op.left = *l;
    n->pos = *pos;

    *l = n;

    return true;
}

static bool json_litex_expr_ast_impl_top(
    struct json_litex_expr_t* expr,
    struct json_litex_expr_ast_impl_t* impl)
{
    struct json_litex_expr_ast_attr_t a;
    size_t s;

    s = JSON_LITEX_EXPR_AST_STACK_SIZE();
    ENSURE(s == 1, "invalid ast stack: "
        "size is %zu", s);

    impl->root = JSON_LITEX_EXPR_AST_STACK_POP();

    // stev: TODO: here is the place where AST
    // rewriting optimizations should be done!

    a = json_litex_expr_ast_gen_code_visit(
            expr, impl->root);
    json_litex_expr_backpatch_ast_attr(
            expr, &a);

    return true;
}

#define JSON_LITEX_EXPR_RANGE_LEN(l)   \
    (                                  \
        STATIC(TYPEOF_IS(l, struct     \
            json_litex_expr_range_t)), \
        PTR_DIFF((l).end, (l).beg)     \
    )
#define JSON_LITEX_EXPR_RANGE(b, e)         \
    ({                                      \
        struct json_litex_expr_range_t __r; \
        __r.beg = b;                        \
        __r.end = e;                        \
        __r;                                \
    })

#define JSON_LITEX_EXPR_CALL_RULE(n, ...) \
    (                                     \
          expr->impl.n ## _rule != NULL   \
        ? expr->impl.n ## _rule(expr,     \
            JSON_LITEX_EXPR_IMPL_PTR(),   \
            ## __VA_ARGS__)               \
        : true                            \
    )
#define JSON_LITEX_EXPR_CALL_NUM_RULE() \
    (                                   \
        JSON_LITEX_EXPR_CALL_RULE(      \
            const_num,                  \
            &expr->prev_tok.pos,        \
            expr->prev_tok.value.num)   \
    )
#define JSON_LITEX_EXPR_CALL_STR_RULE() \
    (                                   \
        JSON_LITEX_EXPR_CALL_LIT_RULE(  \
            const_str)                  \
    )
#define JSON_LITEX_EXPR_CALL_ID_RULE() \
    (                                  \
        JSON_LITEX_EXPR_CALL_LIT_RULE( \
            const_id)                  \
    )
#define JSON_LITEX_EXPR_CALL_CONST_RULE() \
    ({                                    \
          expr->prev_tok.type == TOK(num) \
        ? JSON_LITEX_EXPR_CALL_NUM_RULE() \
        : expr->prev_tok.type == TOK(str) \
        ? JSON_LITEX_EXPR_CALL_STR_RULE() \
        : expr->prev_tok.type == TOK(id)  \
        ? JSON_LITEX_EXPR_CALL_ID_RULE()  \
        : ({ ASSERT(false); false; });    \
    })
#define JSON_LITEX_EXPR_CALL_LIT_RULE_(n, l, p, ...) \
    ({                                               \
        size_t __n =                                 \
            JSON_LITEX_EXPR_RANGE_LEN(l);            \
        JSON_LITEX_EXPR_CALL_RULE(                   \
            n, p, l.beg, __n, ## __VA_ARGS__);       \
    })
#define JSON_LITEX_EXPR_CALL_LIT_RULE(n)  \
        JSON_LITEX_EXPR_CALL_LIT_RULE_(n, \
            expr->prev_tok.lexeme,        \
            &expr->prev_tok.pos)
#define JSON_LITEX_EXPR_CALL_REX_RULE(n)   \
        JSON_LITEX_EXPR_CALL_LIT_RULE_(n,  \
            expr->prev_tok.lexeme,         \
            &expr->prev_tok.pos,           \
            expr->prev_tok.value.rex.opts, \
            expr->prev_tok.value.rex.hash)
#define JSON_LITEX_EXPR_CALL_BUILTIN_RULE(l, p, n) \
        JSON_LITEX_EXPR_CALL_LIT_RULE_(            \
            call_builtin, l, p, n)
#define JSON_LITEX_EXPR_CALL_CMP_RULE(t, p)     \
        JSON_LITEX_EXPR_CALL_RULE(cmp_op, p,    \
            json_litex_expr_token_to_cmp_op(t))
#define JSON_LITEX_EXPR_CALL_TOP_RULE() \
        JSON_LITEX_EXPR_CALL_RULE(top)

#undef  JSON_LITEX_EXPR_ERROR
#define JSON_LITEX_EXPR_ERROR(e)         \
        JSON_LITEX_EXPR_ERROR_(          \
            json_litex_expr_error_ ## e, \
            expr->input_pos,             \
            false)
#define JSON_LITEX_EXPR_ERROR_TOK(e) \
        JSON_LITEX_EXPR_ERROR_(      \
            e, expr->token.pos,      \
            false)

#define JSON_LITEX_EXPR_ERROR_POS(e, d) \
    ({                                  \
        expr->update_pos(expr, d);      \
        JSON_LITEX_EXPR_ERROR(e);       \
    })

#define JSON_LITEX_EXPR_NEXT_TOKEN() \
        json_litex_expr_next_token(expr)

#define JSON_LITEX_EXPR_NEED_TOKEN(...) \
    (                                   \
        json_litex_expr_need_token(     \
            expr, (VA_ARGS_REPEAT(|,    \
                JSON_LITEX_EXPR_TOKEN,  \
                __VA_ARGS__)))          \
    )

#define JSON_LITEX_EXPR_MISSED_TOKEN() \
        json_litex_expr_missed_token(expr)

#define JSON_LITEX_EXPR_EAT_TOKEN()   \
    (                                 \
        expr->prev_tok = expr->token, \
        JSON_LITEX_EXPR_NEXT_TOKEN()  \
    )

#define JSON_LITEX_EXPR_TOKEN(n) \
        json_litex_expr_token_type_ ## n

#define JSON_LITEX_EXPR_PEEK_TOKEN(...) \
    (                                   \
        expr->token.type &              \
        (VA_ARGS_REPEAT(|,              \
            JSON_LITEX_EXPR_TOKEN,      \
            __VA_ARGS__))               \
    )

// stev: TRY_TOKEN has a special behaviour: when
// EAT_TOKEN fails (i.e. NEXT_TOKEN fails), then
// make the encompassing function fail returning
// 'false' to the caller!

#define JSON_LITEX_EXPR_TRY_TOKEN(...)           \
    (                                            \
        JSON_LITEX_EXPR_PEEK_TOKEN(__VA_ARGS__)  \
        ? (                                      \
            ({ if (!JSON_LITEX_EXPR_EAT_TOKEN()) \
                return false; }),                \
            true                                 \
          )                                      \
        : (false)                                \
    )

#define TOK(t) JSON_LITEX_EXPR_TOKEN(t)
#define ERR(n) json_litex_expr_error_ ## n

#define NEXT_TOKEN    JSON_LITEX_EXPR_NEXT_TOKEN
#define NEED_TOKEN    JSON_LITEX_EXPR_NEED_TOKEN
#define NEED_END()    JSON_LITEX_EXPR_NEED_TOKEN(eos)
#define MISSED_TOKEN  JSON_LITEX_EXPR_MISSED_TOKEN
#define EAT_TOKEN     JSON_LITEX_EXPR_EAT_TOKEN
#define PEEK_TOKEN    JSON_LITEX_EXPR_PEEK_TOKEN
#define TRY_TOKEN     JSON_LITEX_EXPR_TRY_TOKEN

#define ERROR         JSON_LITEX_EXPR_ERROR
#define ERROR_TOK     JSON_LITEX_EXPR_ERROR_TOK
#define ERROR_POS     JSON_LITEX_EXPR_ERROR_POS
#define CALL_RULE     JSON_LITEX_EXPR_CALL_RULE
#define CALL_NUM_RULE JSON_LITEX_EXPR_CALL_NUM_RULE
#define CALL_CONST_RULE JSON_LITEX_EXPR_CALL_CONST_RULE
#define CALL_LIT_RULE JSON_LITEX_EXPR_CALL_LIT_RULE
#define CALL_REX_RULE JSON_LITEX_EXPR_CALL_REX_RULE
#define CALL_BUILTIN_RULE \
                      JSON_LITEX_EXPR_CALL_BUILTIN_RULE
#define CALL_CMP_RULE JSON_LITEX_EXPR_CALL_CMP_RULE
#define CALL_TOP_RULE JSON_LITEX_EXPR_CALL_TOP_RULE
#define PARSE(w, ...) json_litex_expr_parse_ ## w( \
                        expr, ## __VA_ARGS__)

#define RANGE_LEN     JSON_LITEX_EXPR_RANGE_LEN
#define RANGE         JSON_LITEX_EXPR_RANGE

static void* json_litex_expr_pcre2_malloc(
    PCRE2_SIZE size, struct json_litex_expr_t* expr)
{
#if defined(__STDC_VERSION__) && \
            __STDC_VERSION__ >= 201112L
    enum { max_align = MEM_ALIGNOF(max_align_t) };
#else
    enum { max_align = 16 };
#endif
    return expr->alloc_func(
        expr->alloc_obj, size, max_align);
}

static void json_litex_expr_pcre2_free(
    void* ptr, struct json_litex_expr_t* expr)
{
    expr->dealloc_func(expr->alloc_obj, ptr);
}

#define json_litex_expr_ast_impl_cmp_in NULL
#define json_litex_expr_ast_impl_and_in NULL
#define json_litex_expr_ast_impl_or_in  NULL

#define JSON_LITEX_EXPR_IMPL_FUNC_INIT_(t, f, s, n) \
    expr->impl.f ## _ ## s = (json_litex_expr_ ## t ## _ ## s ## _t) \
        json_litex_expr_ ## n ## _impl_ ## f

#define JSON_LITEX_EXPR_IMPL_FUNC_INIT(t, f, n) \
        JSON_LITEX_EXPR_IMPL_FUNC_INIT_(t, f, func, n)
#define JSON_LITEX_EXPR_IMPL_RULE_INIT(t, o, n) \
        JSON_LITEX_EXPR_IMPL_FUNC_INIT_(t, o, rule, n)

#define JSON_LITEX_EXPR_INIT_IMPL(n, ...)                     \
    do {                                                      \
        JSON_LITEX_EXPR_IMPL_FUNC_INIT(impl, clear, n);       \
        JSON_LITEX_EXPR_IMPL_FUNC_INIT(impl, done, n);        \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(btv, const_id, n);     \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(num, const_num, n);    \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(str, const_str, n);    \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(str, count_str, n);    \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(rex, count_rex, n);    \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(str, match_str, n);    \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(rex, match_rex, n);    \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(btf, call_builtin, n); \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(nul, cmp_in, n);       \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(cmp, cmp_op, n);       \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(nul, not_op, n);       \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(nul, and_in, n);       \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(nul, and_op, n);       \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(nul, or_in, n);        \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(nul, or_op, n);        \
        JSON_LITEX_EXPR_IMPL_RULE_INIT(top, top, n);          \
        json_litex_expr_ ## n ## _impl_init(                  \
            JSON_LITEX_EXPR_IMPL_AS(n),                       \
            &sizes->n, ## __VA_ARGS__);                       \
    } while (0)

#ifdef JSON_LITEX_TEST_EXPR
static const struct json_litex_expr_builtin_t*
    json_litex_expr_lookup_alpha_builtin(
        struct json_litex_expr_t*,
        const uchar_t*, size_t);
static const struct json_litex_expr_builtin_t*
    json_litex_expr_lookup_def_builtin(
        struct json_litex_expr_t*,
        const uchar_t*, size_t);
#endif // JSON_LITEX_TEST_EXPR

static struct json_litex_expr_t*
    json_litex_expr_create(
        enum json_litex_expr_opts_t opts,
        enum json_litex_expr_impl_type_t type,
        const struct json_litex_expr_sizes_t* sizes,
        json_litex_expr_alloc_func_t alloc_func,
        json_litex_expr_dealloc_func_t dealloc_func,
        void* alloc_obj)
{
    const size_t n =
        sizeof(struct json_litex_expr_t);
    struct json_litex_expr_t* expr;

    expr = malloc(n);
    VERIFY(expr != NULL);
    memset(expr, 0, n);

#ifdef JSON_DEBUG
    expr->debug = SIZE_TRUNC_BITS(
        json_litex_debug_get_level(
            json_litex_debug_expr_class),
        debug_bits);
#endif

    expr->opts = opts;
    expr->impl.type = type;
    expr->alloc_obj = alloc_obj;
    expr->alloc_func = alloc_func;
    expr->dealloc_func = dealloc_func;

    expr->pcre2_gc =
        pcre2_general_context_create(
            (void* (*)(PCRE2_SIZE, void*))
            json_litex_expr_pcre2_malloc,
            (void (*)(void*, void*))
            json_litex_expr_pcre2_free,
            expr);
    ASSERT(expr->pcre2_gc != NULL);

    expr->pcre2_cc =
        pcre2_compile_context_create(
            expr->pcre2_gc);
    ASSERT(expr->pcre2_cc != NULL);

#ifdef JSON_LITEX_TEST_EXPR
    expr->lookup =
          JSON_LITEX_EXPR_OPTS_IS(alpha_builtins)
        ? json_litex_expr_lookup_alpha_builtin
        : json_litex_expr_lookup_def_builtin;
#endif // JSON_LITEX_TEST_EXPR

    json_litex_expr_nodes_vector_init(
        &expr->nodes,
        sizes->nodes_max,
        sizes->nodes_init);
    json_litex_expr_codes_vector_init(
        &expr->codes,
        sizes->rexes_max,
        sizes->rexes_init);
    json_litex_expr_rexes_lhash_init(
        &expr->rexes,
        sizes->rexes_max,
        sizes->rexes_init);

    switch (type) {
    case json_litex_expr_flat_impl_type:
        JSON_LITEX_EXPR_INIT_IMPL(flat);
        break;
    case json_litex_expr_ast_impl_type:
        JSON_LITEX_EXPR_INIT_IMPL(ast);
        break;
    default:
        UNEXPECT_VAR("%d", type);
    }

    return expr;
}

#define JSON_LITEX_EXPR_CALL_IMPL(f, ...)   \
    do {                                    \
        ASSERT(                             \
            expr->impl.f ## _func != NULL); \
        expr->impl.f ## _func(              \
            JSON_LITEX_EXPR_IMPL_PTR(),     \
            ## __VA_ARGS__);                \
    } while (0)

static void json_litex_expr_destroy(
    struct json_litex_expr_t* expr)
{
    JSON_LITEX_EXPR_CALL_IMPL(done);

    json_litex_expr_rexes_lhash_done(
        &expr->rexes);
    json_litex_expr_codes_vector_done(
        &expr->codes);
    json_litex_expr_nodes_vector_done(
        &expr->nodes);

    pcre2_compile_context_free(
        expr->pcre2_cc);
    pcre2_general_context_free(
        expr->pcre2_gc);

    free(expr);
}

#define JSON_LITEX_EXPR_CLEAR(n)              \
    do {                                      \
        memset(&expr->n, 0, sizeof(expr->n)); \
    } while (0)

static void json_litex_expr_clear(
    struct json_litex_expr_t* expr)
{
    JSON_LITEX_EXPR_CALL_IMPL(clear);

    json_litex_expr_nodes_vector_clear(
        &expr->nodes);

    JSON_LITEX_EXPR_CLEAR(input);
    JSON_LITEX_EXPR_CLEAR(input_ptr);
    JSON_LITEX_EXPR_CLEAR(input_pos);
    JSON_LITEX_EXPR_CLEAR(update_pos);
    JSON_LITEX_EXPR_CLEAR(token);
    JSON_LITEX_EXPR_CLEAR(prev_tok);
    JSON_LITEX_EXPR_CLEAR(error);
    JSON_LITEX_EXPR_CLEAR(def);
}

static const struct json_litex_expr_rex_def_t*
    json_litex_expr_get_rexes(
        struct json_litex_expr_t* expr)
{
    if (expr->rdef == NULL &&
        expr->codes.n_elems > 0)
        expr->rdef =
            json_litex_expr_new_rex_def(expr);

    return expr->rdef;
}

#undef  CASE
#define CASE(n) \
    case json_litex_expr_error_ ## n

static void json_litex_expr_print_error_desc(
    const struct json_litex_expr_error_t* error,
    FILE* file)
{
    switch (error->type) {

    CASE(none):
        fputs("none", file);
        break;
    CASE(unexpected_char):
        fputs("unexpected char", file);
        break;
    CASE(unexpected_token):
        fputs("unexpected token", file);
        break;
    CASE(unexpected_end_of_expr):
        fputs("unexpected end of expression", file);
        break;
    CASE(empty_string):
        fputs("empty string", file);
        break;
    CASE(empty_regex):
        fputs("empty regex", file);
        break;
    CASE(unknown_regex_opt):
        fputs("unknown regex option char", file);
        break;
    CASE(dup_regex_opt):
        fputs("duplicated regex option char", file);
        break;
    CASE(invalid_number):
        fputs("invalid number", file);
        break;
    CASE(invalid_operator):
        fputs("invalid operator", file);
        break;
    CASE(unknown_builtin):
        fputs("unknown builtin", file);
        break;
    CASE(builtin_not_val):
        fprintf(file, "builtin '%s' is not a value",
            error->builtin_not_val.builtin->name);
        break;
    CASE(builtin_not_func):
        fprintf(file, "builtin '%s' is not a function",
            error->builtin_not_func.builtin->name);
        break;
    CASE(builtin_func_n_args): {
        const struct json_litex_expr_builtin_t* b =
            error->builtin_func_n_args.builtin;
        const struct json_litex_expr_builtin_func_t* f =
            JSON_LITEX_EXPR_BUILTIN_AS_CONST(b, func);

        fprintf(file, "builtin function '%s': "
            "expected %zu arg%s, but got %zu",
            b->name, f->n_args, f->n_args != 1 ? "s" : "",
            error->builtin_func_n_args.arg);
        break;
    }
    CASE(builtin_func_arg_type): {
        const struct json_litex_expr_builtin_t* b =
            error->builtin_func_arg_type.builtin;
        const struct json_litex_expr_builtin_func_t* f =
            JSON_LITEX_EXPR_BUILTIN_AS_CONST(b, func);
        size_t k = error->builtin_func_arg_type.arg;

        fprintf(file, "builtin function '%s': "
            "arg #%zu type mismatch: expected a %s", b->name, k + 1,
              JSON_LITEX_EXPR_BUILTIN_FUNC_ARG_IS(f->args, num, k)
            ? "number"
            : JSON_LITEX_EXPR_BUILTIN_FUNC_ARG_IS(f->args, str, k)
            ? "string"
            : ({ ASSERT(false); "???"; }));
        break;
    }
    CASE(pcre2_compile): {
        char b[256];
        int r;

        r = pcre2_get_error_message(
                error->pcre2, PTR_UCHAR_CAST(b),
                ARRAY_SIZE(b));

        fprintf(file, "pcre2 compile: %s",
            r != PCRE2_ERROR_BADDATA ? b : "???");
        break;
    }

    default:
        UNEXPECT_VAR("%d", error->type);
    }
}

#if SIZE_MAX < ULONG_MAX
static inline size_t strtosz(
    const char* ptr, char** end, int base)
{
    unsigned long r;

    errno = 0;
    r = strtoul(ptr, end, base);

    if (errno == 0 && r > SIZE_MAX)
        errno = ERANGE;

    return r;
}
#define STR_TO_SIZE strtosz
#elif SIZE_MAX == ULONG_MAX
#define STR_TO_SIZE strtoul
#elif SIZE_MAX == UULONG_MAX
#define STR_TO_SIZE strtoull
#else
#error unexpected SIZE_MAX > UULONG_MAX
#endif

static inline bool
    json_litex_expr_parse_num(
        const uchar_t* ptr, const uchar_t** end,
        json_litex_expr_value_t* result)
{
    errno = 0;
    STATIC(TYPE_IS_SIZET(json_litex_expr_value_t));
    *result = STR_TO_SIZE(PTR_CHAR_CAST_CONST(ptr),
        (char**) end, 10);

    return errno == 0;
}

#define escapes json_litex_escapes

static bool json_litex_expr_next_token(
    struct json_litex_expr_t* expr)
{
    static const size_t tokens[128] = {
        ['|'] = TOK(or),
        ['&'] = TOK(and),
        ['!'] = TOK(not),
        ['='] = TOK(eq),
        ['<'] = TOK(lt),
        ['>'] = TOK(gt),
        [','] = TOK(comma),
        ['('] = TOK(lparen),
        [')'] = TOK(rparen),
        ['#'] = TOK(pound),
        ['0'...'9'] = TOK(num),
        ['a'...'z'] = TOK(id),
        ['A'...'Z'] = TOK(id),
        ['_'] = TOK(id),
        ['`'] = TOK(str),
        ['\''] = TOK(rex),
        ['/'] = TOK(rex),
    };
#define OPT(n) \
    (json_litex_expr_rex_opt_ ## n + 1)
    static const size_t opts[128] = {
        ['I'] = OPT(ignore_case),
        ['S'] = OPT(dot_match_all),
        ['U'] = OPT(no_utf8_check),
        ['X'] = OPT(extended_pat),
    };
#undef OPT

    const uchar_t* p;
    size_t t, n = 0;

    ASSERT(
        expr->input_ptr <=
        expr->input.end);

    for (p = expr->input_ptr;
         p < expr->input.end;
         p ++,
         n ++) {
        if (!ISASCII(*p) ||
            !ISSPACE(*p))
            break;

        if (!expr->is_raw)
            n += escapes[*p];
    }

    expr->update_pos(expr, n);
    expr->input_ptr = p;

    if (p >= expr->input.end) {
        expr->token.type = TOK(eos);
        expr->token.lexeme = RANGE(p, p);
        expr->token.pos = expr->input_pos;
    }
    else
    if (ISASCII(*p) && (t = tokens[*p])) {
        const uchar_t *q = p + 1;
        fnv_hash_t h;
        uchar_t c;
        size_t d;

        if (!expr->is_raw)
            ASSERT(!escapes[*p]);
        n = 0;

        switch (t) {

        case TOK(or):
        case TOK(and):
        case TOK(eq):
            if (q >= expr->input.end || *q != *p)
                return ERROR(invalid_operator);
            q ++;
            break;

        case TOK(not):
            if (q < expr->input.end && *q == '=') {
                t = TOK(ne);
                q ++;
            }
            break;

        case TOK(lt):
            if (q < expr->input.end && *q == '=') {
                t = TOK(le);
                q ++;
            }
            break;

        case TOK(gt):
            if (q < expr->input.end && *q == '=') {
                t = TOK(ge);
                q ++;
            }
            break;

        case TOK(comma):
        case TOK(lparen):
        case TOK(rparen):
        case TOK(pound):
            break;

        case TOK(num):
            if (!json_litex_expr_parse_num(
                    p, &q, &expr->token.value.num))
                return ERROR(invalid_number);
            break;

        case TOK(str):
        case TOK(rex):
            c = *p;

            if (t == TOK(rex))
                FNV_HASH_INIT(h);

            while (q < expr->input.end) {
                if (!expr->is_raw && ISASCII(*q))
                    n += escapes[*q];

                if (*q == c) {
                    const uchar_t* r = q + 1;

                    if (r >= expr->input.end ||
                        *r != c)
                        break;
                    if (t == TOK(rex))
                        FNV_HASH_ADD(h, *q);

                    q ++;
                }
                if (t == TOK(rex))
                    FNV_HASH_ADD(h, *q);

                q ++;
            }
            d = PTR_DIFF(q, p);

            if (q >= expr->input.end) {
                ASSERT_SIZE_ADD_NO_OVERFLOW(
                    d, n);
                return ERROR_POS(
                    unexpected_end_of_expr,
                    d + n);
            }

            if (d == 1)
                return c == '`'
                    ? ERROR(empty_string)
                    : ERROR(empty_regex);

            // stev: an important general assuption
            // about litex strings and regexes: they
            // aren't supposed to contain NUL chars:
#if 0
            // stev: no need to generate an error:
            // (1) normal JSON strings contain NUL
            // chars in escaped form '\u0000' only;
            // but JSON parser's config parameter
            // 'json_disallow_unicode_esc_config'
            // was already enabled below;
            // (2) raw JSON strings cannot contain
            // NUL chars by definition (see source
            // file 'lib/json.c')
            if ((r = memchr(p, 0, d)))
                return ERROR_POS(embedded_null, r);
#endif

            ASSERT(*q == c);
            q ++;

            if (t == TOK(rex)) {
                const uchar_t* r = q;
                size_t o = 0;

                while (q < expr->input.end &&
                       ISALPHA(*q)) {
                    size_t b = opts[*q];

                    if (b == 0) {
                        d = PTR_DIFF(q, p);
                        return ERROR_POS(
                            unknown_regex_opt, d);
                    }

                    ASSERT(b <= SIZE_BIT);
                    // <=> b - 1 < SIZE_BIT
                    b = SZ(1) << (b - 1);

                    if (o & b) {
                        d = PTR_DIFF(q, p);
                        return ERROR_POS(
                            dup_regex_opt, d);
                    }

                    o |= b;
                    q ++;
                }

                expr->token.lexeme = RANGE(p, r);
                expr->token.value.rex.opts = o;
                expr->token.value.rex.hash = h;
                goto next_tok;
            }
            break;

        case TOK(id):
            while (ISALNUM(*q) || (*q == '_'))
                ++ q;
            break;

        default:
            UNEXPECT_VAR("%zu", t);
        }

        expr->token.lexeme = RANGE(p, q);
    next_tok:
        expr->token.pos = expr->input_pos;
        expr->token.type = t;

        d = PTR_DIFF(q, p);
        SIZE_ADD_EQ(d, n);
        expr->update_pos(expr, d);
        expr->input_ptr = q;
    }
    else
        return ERROR(unexpected_char);

    return true;
}

#undef escapes

static bool json_litex_expr_missed_token(
    struct json_litex_expr_t* expr)
{
    return ERROR_TOK(
        expr->token.type == TOK(eos)
        ? ERR(unexpected_end_of_expr)
        : ERR(unexpected_token));
}

static bool json_litex_expr_need_token(
    struct json_litex_expr_t* expr,
    enum json_litex_expr_token_type_t type)
{
    if (!(expr->token.type & type))
        return MISSED_TOKEN();
    if (expr->token.type != TOK(eos))
        return EAT_TOKEN();
    else
        return true;
}

#define INT_IS_POW2_(m, n)         \
    ({                             \
        m(n > 0);                  \
        (n) && !((n) & ((n) - 1)); \
    })
#define CONST_INT_IS_POW2(n)          \
    (                                 \
        STATIC(TYPEOF_IS_INTEGER(n)), \
        STATIC(IS_CONSTANT(n)),       \
        INT_IS_POW2_(STATIC, n),      \
    )
#define INT_IS_POW2(n)                \
    ({                                \
        STATIC(TYPEOF_IS_INTEGER(n)); \
        INT_IS_POW2_(ASSERT, n);      \
    })

// int __builtin_ctz(unsigned int),
// int __builtin_ctzl(unsigned long):
// Returns the number of trailing 0-bits in x, starting at the least
// significant bit position. If x is 0, the result is undefined.
// https://gcc.gnu.org/onlinedocs/gcc-4.3.4/gcc/Other-Builtins.html

#if INTMAX_MAX == INT_MAX
#define INT_CTZ(x)                   \
    (                                \
        STATIC(INT_MAX <= SIZE_MAX), \
        (size_t) __builtin_ctz(x)    \
    )
#elif INTMAX_MAX == LONG_MAX
#define INT_CTZ(x)                   \
    (                                \
        STATIC(INT_MAX <= SIZE_MAX), \
        (size_t) __builtin_ctzl(x)   \
    )
#else
#error intmax_t is neither int nor long
#endif

// stev: note that the macro 'INT_LOG2'
// below have disimilar functionality
// w.r.t. that of macro 'SIZE_LOG2' in
// header 'int-traits.h': a precodition
// of 'INT_LOG2' is that its arg be a
// power of two; the macro 'SIZE_LOG2'
// does not impose such requirement!

#define INT_LOG2_(m, n)     \
    ({                      \
        INT_IS_POW2_(m, n); \
        INT_CTZ(n);         \
    })
#define CONST_INT_LOG2(n)             \
    (                                 \
        STATIC(TYPEOF_IS_INTEGER(n)), \
        STATIC(IS_CONSTANT(n)),       \
        INT_LOG2_(STATIC, n)          \
    )
#define INT_LOG2(n)                   \
    ({                                \
        STATIC(TYPEOF_IS_INTEGER(n)); \
        INT_LOG2_(ASSERT, n);         \
    })

static enum json_litex_expr_cmp_op_t
    json_litex_expr_token_to_cmp_op(
        enum json_litex_expr_token_type_t token)
{
    const size_t eq = CONST_INT_LOG2(TOK(eq));
    size_t t = INT_LOG2(token);

    STATIC(
        TOK(eq) * 2 == TOK(ne) &&
        TOK(ne) * 2 == TOK(lt) &&
        TOK(lt) * 2 == TOK(le) &&
        TOK(le) * 2 == TOK(gt) &&
        TOK(gt) * 2 == TOK(ge));

#define CMP(n) json_litex_expr_cmp_op_ ## n
    STATIC(
        CMP(eq) + 1 == CMP(ne) &&
        CMP(ne) + 1 == CMP(lt) &&
        CMP(lt) + 1 == CMP(le) &&
        CMP(le) + 1 == CMP(gt) &&
        CMP(gt) + 1 == CMP(ge));
#undef CMP

    if (t < eq || (t - eq) >= 6)
        UNEXPECT_VAR("%d", token);

    return
        json_litex_expr_cmp_op_eq +
        (t - eq);
}

static bool json_litex_expr_parse_expr(
    struct json_litex_expr_t*);

// args : [ arg ( "," arg )* ]
//      ;
// arg  : NUM
//      | STR
//      | ID
//      ;
static bool json_litex_expr_parse_args(
    struct json_litex_expr_t* expr,
    size_t* n_args)
{
    size_t n = 0;

    if (TRY_TOKEN(num, str, id)) {
        if (!CALL_CONST_RULE())
            return false;
        n ++;

        while (TRY_TOKEN(comma)) {
            if (!NEED_TOKEN(num, str, id) ||
                !CALL_CONST_RULE())
                return false;
            n ++;
        }
    }

    *n_args = n;
    return true;
}

// prim : "!" prim
//      | "(" expr ")"
//      | "#" ( STR | REX )
//      | ID [ "(" args ")" ]
//      | STR
//      | REX
//      | NUM
//      ;
static bool json_litex_expr_parse_prim(
    struct json_litex_expr_t* expr)
{
    struct json_litex_expr_range_t l;
    struct json_text_pos_t p;
    size_t n;

    if (TRY_TOKEN(not)) {
        p = expr->prev_tok.pos;

        if (!PARSE(prim))
            return false;

        return CALL_RULE(not_op, &p);
    }
    else
    if (TRY_TOKEN(lparen))
        return
            PARSE(expr) &&
            NEED_TOKEN(rparen);
    else
    if (TRY_TOKEN(pound)) {
        if (TRY_TOKEN(str))
            return CALL_LIT_RULE(
                count_str);
        else
        if (TRY_TOKEN(rex))
            return CALL_REX_RULE(
                count_rex);
        else
            return MISSED_TOKEN();
    }
    else
    if (TRY_TOKEN(id)) {
        l = expr->prev_tok.lexeme;
        p = expr->prev_tok.pos;
        n = 0;

        if (TRY_TOKEN(lparen) &&
            !(PARSE(args, &n) &&
              NEED_TOKEN(rparen)))
            return false;

        return CALL_BUILTIN_RULE(
            l, &p, n);
    }
    else
    if (TRY_TOKEN(num))
        return CALL_NUM_RULE();
    else
    if (TRY_TOKEN(str))
        return CALL_LIT_RULE(match_str);
    else
    if (TRY_TOKEN(rex))
        return CALL_REX_RULE(match_rex);
    else
        return MISSED_TOKEN();
}

// rel : prim ( ( "<" | ">" | "<=" | ">=" ) prim )*
//     ;
static bool json_litex_expr_parse_rel(
    struct json_litex_expr_t* expr)
{
    enum json_litex_expr_token_type_t t;
    struct json_text_pos_t p;

    if (!PARSE(prim))
        return false;

    while (TRY_TOKEN(lt, le, gt, ge)) {
        t = expr->prev_tok.type;
        p = expr->prev_tok.pos;

        if (!CALL_RULE(cmp_in, &p) ||
            !PARSE(prim) ||
            !CALL_CMP_RULE(t, &p))
            return false;
    }

    return true;
}

// cmp : rel ( ( "==" | "!=" ) rel )*
//     ;
static bool json_litex_expr_parse_cmp(
    struct json_litex_expr_t* expr)
{
    enum json_litex_expr_token_type_t t;
    struct json_text_pos_t p;

    if (!PARSE(rel))
        return false;

    while (TRY_TOKEN(eq, ne)) {
        t = expr->prev_tok.type;
        p = expr->prev_tok.pos;

        if (!CALL_RULE(cmp_in, &p) ||
            !PARSE(rel) ||
            !CALL_CMP_RULE(t, &p))
            return false;
    }

    return true;
}

// term : cmp ( "&&" cmp )*
//      ;
static bool json_litex_expr_parse_term(
    struct json_litex_expr_t* expr)
{
    struct json_text_pos_t p;

    if (!PARSE(cmp))
        return false;

    while (TRY_TOKEN(and)) {
        p = expr->prev_tok.pos;

        if (!CALL_RULE(and_in, &p) ||
            !PARSE(cmp) ||
            !CALL_RULE(and_op, &p))
            return false;
    }

    return true;
}

// expr : term ( "||" term )*
//      ;
static bool json_litex_expr_parse_expr(
    struct json_litex_expr_t* expr)
{
    struct json_text_pos_t p;

    if (!PARSE(term))
        return false;

    while (TRY_TOKEN(or)) {
        p = expr->prev_tok.pos;

        if (!CALL_RULE(or_in, &p) ||
            !PARSE(term) ||
            !CALL_RULE(or_op, &p))
            return false;
    }

    return true;
}

static void json_litex_expr_update_pos_raw(
    struct json_litex_expr_t* expr,
    size_t len)
{
    json_text_update_pos(
        &expr->input_pos,
        expr->input_ptr,
        len);
}

static void json_litex_expr_update_pos(
    struct json_litex_expr_t* expr,
    size_t len)
{
    ASSERT_SIZE_ADD_NO_OVERFLOW(
        expr->input_pos.col,
        len);
    expr->input_pos.col += len;
}

static bool json_litex_expr_compile(
    struct json_litex_expr_t* expr,
    const uchar_t* text, size_t len,
    const struct json_text_pos_t* pos, size_t offset,
    const struct json_litex_expr_def_t** result)
{
    ASSERT(text != NULL);
    ASSERT(offset > 0);
    ASSERT(len > 0);

#ifdef JSON_DEBUG
    PRINT_DEBUG_BEGIN_COND(> 1, "expr=");
    pretty_print_string(
        stderr, text, len,
        pretty_print_string_quotes);
    PRINT_DEBUG_END();
#endif

    json_litex_expr_clear(expr);

    expr->input_ptr =
    expr->input.beg =
    expr->input.end = text;
    expr->input.end += len;
    expr->input_pos = *pos;

    // stev: account for string
    // content starting at 'offset'
    ASSERT_SIZE_ADD_NO_OVERFLOW(
        expr->input_pos.col, offset);
    expr->input_pos.col += offset;

    expr->token.type = TOK(bos);
    expr->token.lexeme = RANGE(
        expr->input_ptr,
        expr->input_ptr);

#ifdef JSON_LITEX_TEST_EXPR
    expr->is_raw = true;
#else
    expr->is_raw = offset > 1;
#endif
    expr->update_pos = expr->is_raw
        ? json_litex_expr_update_pos_raw
        : json_litex_expr_update_pos;

    if (!NEXT_TOKEN() ||
        !PARSE(expr) ||
        !NEED_END())
        return false;

    CALL_TOP_RULE();

    *result = expr->def =
        json_litex_expr_new_def(expr);

    return true;
}

#undef RANGE
#undef RANGE_LEN

#undef PARSE
#undef CALL_TOP_RULE
#undef CALL_CMP_RULE
#undef CALL_BUILTIN_RULE
#undef CALL_REX_RULE
#undef CALL_LIT_RULE
#undef CALL_CONST_RULE
#undef CALL_NUM_RULE
#undef CALL_RULE
#undef ERROR_POS
#undef ERROR_TOK
#undef ERROR

#undef TRY_TOKEN
#undef PEEK_TOKEN
#undef EAT_TOKEN
#undef MISSED_TOKEN
#undef NEED_END
#undef NEED_TOKEN
#undef NEXT_TOKEN

#undef ERR
#undef TOK

enum json_litex_value_type_t
{
    json_litex_value_null_type,
    json_litex_value_boolean_type,
    json_litex_value_number_type,
    json_litex_value_string_type,
};

struct json_litex_string_t
{
    const uchar_t* ptr;
    size_t len;
};

struct json_litex_value_t
{
    enum json_litex_value_type_t   type;
    union {
        // ...                     null;
        bool                       boolean;
        struct json_litex_string_t number;
        struct json_litex_string_t string;
    } u;
};

#define JSON_LITEX_TYPEOF_IS_VALUE_(q, p) \
    TYPEOF_IS(p, q struct json_litex_value_t*)

#define JSON_LITEX_TYPEOF_IS_VALUE(p) \
        JSON_LITEX_TYPEOF_IS_VALUE_(, p)

#define JSON_LITEX_TYPEOF_IS_VALUE_CONST(p) \
        JSON_LITEX_TYPEOF_IS_VALUE_(const, p)

#define JSON_LITEX_VALUE_IS_(q, p, n)                \
    (                                                \
        STATIC(JSON_LITEX_TYPEOF_IS_VALUE_(q, p)),   \
        (p)->type == json_litex_value_ ## n ## _type \
    )
#define JSON_LITEX_VALUE_IS(p, n) \
        JSON_LITEX_VALUE_IS_(, p, n)
#define JSON_LITEX_VALUE_IS_CONST(p, n) \
        JSON_LITEX_VALUE_IS_(const, p, n)

#define JSON_LITEX_VALUE_AS_(q, p, n)          \
    ({                                         \
        ASSERT(JSON_LITEX_VALUE_IS_(q, p, n)); \
        (p)->node.n;                           \
    })
#define JSON_LITEX_VALUE_AS(p, n) \
        JSON_LITEX_VALUE_AS_(, p, n)
#define JSON_LITEX_VALUE_AS_CONST(p, n) \
        JSON_LITEX_VALUE_AS_(const, p, n)

#define JSON_LITEX_VALUE_AS_IF_STRING(v)              \
    ({                                                \
           (v)->type == json_litex_value_number_type  \
        ? &(v)->u.number                              \
        :  (v)->type == json_litex_value_string_type  \
        ? &(v)->u.string                              \
        :  (v)->type != json_litex_value_null_type && \
           (v)->type != json_litex_value_boolean_type \
        ? ({ UNEXPECT_VAR("%d", (v)->type); NULL; })  \
        : NULL;                                       \
    })

enum json_litex_expr_obj_type_t
{
    json_litex_expr_obj_num,
    json_litex_expr_obj_str,
};

struct json_litex_expr_obj_t
{
    enum json_litex_expr_obj_type_t type;
    union {
        json_litex_expr_value_t num;
        const uchar_t*          str;
    };
};

#define JSON_LITEX_EXPR_TYPEOF_IS_OBJ_(q, p) \
    TYPEOF_IS(p, q struct json_litex_expr_obj_t*)

#define JSON_LITEX_EXPR_TYPEOF_IS_OBJ(p) \
        JSON_LITEX_EXPR_TYPEOF_IS_OBJ_(, p)

#define JSON_LITEX_EXPR_TYPEOF_IS_OBJ_CONST(p) \
        JSON_LITEX_EXPR_TYPEOF_IS_OBJ_(const, p)

#define JSON_LITEX_EXPR_OBJ_IS_(q, p, n)              \
    (                                                 \
        STATIC(JSON_LITEX_EXPR_TYPEOF_IS_OBJ_(q, p)), \
        (p)->type == json_litex_expr_obj_ ## n        \
    )
#define JSON_LITEX_EXPR_OBJ_IS(p, n) \
        JSON_LITEX_EXPR_OBJ_IS_(, p, n)
#define JSON_LITEX_EXPR_OBJ_IS_CONST(p, n) \
        JSON_LITEX_EXPR_OBJ_IS_(const, p, n)

#define JSON_LITEX_EXPR_OBJ_AS_(q, p, n)          \
    ({                                            \
        ASSERT(JSON_LITEX_EXPR_OBJ_IS_(q, p, n)); \
        (p)->n;                                   \
    })
#define JSON_LITEX_EXPR_OBJ_AS(p, n) \
        JSON_LITEX_EXPR_OBJ_AS_(, p, n)
#define JSON_LITEX_EXPR_OBJ_AS_CONST(p, n) \
        JSON_LITEX_EXPR_OBJ_AS_(const, p, n)

#define JSON_LITEX_EXPR_OBJ_AS_REF_(q, p, n)      \
    ({                                            \
        ASSERT(JSON_LITEX_EXPR_OBJ_IS_(q, p, n)); \
        &(p)->n;                                  \
    })
#define JSON_LITEX_EXPR_OBJ_AS_REF(p, n) \
        JSON_LITEX_EXPR_OBJ_AS_REF_(, p, n)
#define JSON_LITEX_EXPR_OBJ_AS_REF_CONST(p, n) \
        JSON_LITEX_EXPR_OBJ_AS_REF_(const, p, n)

#undef  STACK_NAME
#define STACK_NAME      json_litex_expr_vm
#undef  STACK_ELEM_TYPE
#define STACK_ELEM_TYPE struct json_litex_expr_obj_t

#define STACK_NEED_MAX_SIZE
#include "stack-impl.h"
#undef  STACK_NEED_MAX_SIZE

#define JSON_LITEX_EXPR_VM_STACK_OP(o, ...) \
    STACK_ ## o(&vm->stack, ## __VA_ARGS__)

#define JSON_LITEX_EXPR_VM_STACK_PUSH(t, v)  \
    ({                                       \
        struct json_litex_expr_obj_t __o;    \
        __o.type = json_litex_expr_obj_ ##t; \
        __o.t = v;                           \
        JSON_LITEX_EXPR_VM_STACK_OP(         \
            PUSH, __o);                      \
    })

#define JSON_LITEX_EXPR_VM_STACK_POP_AS(n)    \
    ({                                        \
        struct json_litex_expr_obj_t __o;     \
        __o = JSON_LITEX_EXPR_VM_STACK_POP(); \
        JSON_LITEX_EXPR_OBJ_AS(&__o, n);      \
    })
#define JSON_LITEX_EXPR_VM_STACK_TOP_AS(n)        \
    ({                                            \
        struct json_litex_expr_obj_t* __o;        \
        __o = JSON_LITEX_EXPR_VM_STACK_TOP_REF(); \
        JSON_LITEX_EXPR_OBJ_AS(__o, n);           \
    })
#define JSON_LITEX_EXPR_VM_STACK_TOP_REF_AS(n)    \
    ({                                            \
        struct json_litex_expr_obj_t* __o;        \
        __o = JSON_LITEX_EXPR_VM_STACK_TOP_REF(); \
        JSON_LITEX_EXPR_OBJ_AS_REF(__o, n);       \
    })

#define JSON_LITEX_EXPR_VM_STACK_SIZE() \
        JSON_LITEX_EXPR_VM_STACK_OP(SIZE)
#define JSON_LITEX_EXPR_VM_STACK_POP() \
        JSON_LITEX_EXPR_VM_STACK_OP(POP)
#define JSON_LITEX_EXPR_VM_STACK_POP_N(n) \
        JSON_LITEX_EXPR_VM_STACK_OP(POP_N, SZ(n))
#define JSON_LITEX_EXPR_VM_STACK_TOP_REF() \
        JSON_LITEX_EXPR_VM_STACK_OP(TOP_REF)
#define JSON_LITEX_EXPR_VM_STACK_CLEAR() \
        JSON_LITEX_EXPR_VM_STACK_OP(CLEAR)

struct json_litex_expr_vm_sizes_t
{
    size_t ovector_max;
    size_t match_limit;
    size_t depth_limit;
    // stev: limit given in kilobytes
    size_t heap_limit;
    size_t stack_max;
    size_t stack_init;
};

#define JSON_LITEX_EXPR_VM_SIZES_VALIDATE_(o)    \
    do {                                         \
        JSON_SIZE_VALIDATE(o ovector_max);       \
        JSON_SIZE_VALIDATE(o match_limit);       \
        JSON_SIZE_VALIDATE(o depth_limit);       \
        JSON_SIZE_VALIDATE(o heap_limit);        \
        JSON_SIZE_VALIDATE(o stack_max);         \
        JSON_SIZE_VALIDATE(o stack_init);        \
    } while (0)
#define JSON_LITEX_EXPR_VM_SIZES_VALIDATE(p)     \
    do {                                         \
        STATIC(JSON_TYPEOF_IS_SIZES(p,           \
            litex_expr_vm));                     \
        JSON_LITEX_EXPR_VM_SIZES_VALIDATE_(p->); \
    } while (0)

enum json_litex_expr_vm_error_type_t
{
    json_litex_expr_vm_error_none,
    json_litex_expr_vm_error_len_of_null,
    json_litex_expr_vm_error_len_of_bool,
    json_litex_expr_vm_error_invalid_int_arg,
    json_litex_expr_vm_error_invalid_uint_arg,
    json_litex_expr_vm_error_pcre2_ovector,
    json_litex_expr_vm_error_pcre2_decode,
    json_litex_expr_vm_error_pcre2_match,
};

struct json_litex_expr_vm_error_t
{
    enum json_litex_expr_vm_error_type_t type;
    int pcre2;
};

#ifdef JSON_LITEX_TEST_EXPR
struct json_litex_expr_vm_alpha_t
{
    size_t vals['z' - 'a' + 1];
};
#endif

struct json_litex_expr_vm_t
{
    struct json_litex_expr_vm_error_t   error;
    struct json_litex_expr_vm_stack_t   stack;
    pcre2_match_context*                mctxt;
    pcre2_match_data*                   mdata;
    pcre2_code**                        codes;
    size_t                              n_codes;
    bits_t                              free_codes: 1;
#ifdef JSON_LITEX_TEST_EXPR
    struct json_litex_expr_vm_alpha_t   alpha;
#endif
    const struct json_litex_expr_def_t* expr;
    const struct json_litex_value_t*    val;
    size_t                              ip;
};

#define JSON_LITEX_EXPR_VM_ERROR_IS(n) \
    (                                  \
        vm->error.type ==              \
        json_litex_expr_vm_error_ ## n \
    )
#define JSON_LITEX_EXPR_VM_ERROR(n)         \
    ({                                      \
        vm->error.type =                    \
            json_litex_expr_vm_error_ ## n; \
        false;                              \
    })
#define JSON_LITEX_EXPR_VM_PCRE2_ERROR(n, e)      \
    ({                                            \
        vm->error.type =                          \
            json_litex_expr_vm_error_pcre2_ ## n; \
        vm->error.pcre2 = e;                      \
        false;                                    \
    })

#define TYPEOF_IS_CHAR_ARRAY(x)       \
    (                                 \
        TYPEOF_IS(x, const char[]) || \
        TYPEOF_IS(x, char[])          \
    )
#define STR_COPY(d, s)                \
    (                                 \
        STATIC(                       \
            TYPEOF_IS_CHAR_ARRAY(d)), \
        STATIC(                       \
            TYPEOF_IS_CHAR_ARRAY(s)), \
        STATIC(                       \
            ARRAY_SIZE(d) >           \
            ARRAY_SIZE(s)),           \
        strcpy(d, s)                  \
    )

#undef  CASE
#define CASE(n) \
    case json_litex_expr_vm_error_ ## n

static void json_litex_expr_vm_error_print_desc(
    const struct json_litex_expr_vm_error_t* error,
    FILE* file)
{
    size_t k = 0;
    char b[256];
    int r;

PRAGMA_DIAG_PUSH_IGNORED_IMPLICIT_FALLTHROUGH

    switch (error->type) {

    CASE(none):
        fputs("none", file);
        break;

    CASE(len_of_null):
        k ++;
    CASE(len_of_bool):
        fprintf(file, "function 'len' applied to '%s'",
            k ? "null" : "boolean");
        break;

    CASE(invalid_int_arg):
        k ++;
    CASE(invalid_uint_arg):
        fprintf(file, "invalid argument of function '%s'",
            k ? "int" : "uint");
        break;

    CASE(pcre2_ovector):
        STR_COPY(b, "too many capturing subpatterns");
        goto pcre2_error;

    CASE(pcre2_decode):
        k ++;
    CASE(pcre2_match):
        r = pcre2_get_error_message(
                error->pcre2, PTR_UCHAR_CAST(b),
                ARRAY_SIZE(b));
        if (r == PCRE2_ERROR_BADDATA)
            STR_COPY(b, "???");

    pcre2_error:
        fprintf(file, "pcre2 function '%s' failed: %s",
            k ? "decode" : "match", b);
        break;

    default:
        UNEXPECT_VAR("%d", error->type);
    }

PRAGMA_DIAG_POP_IGNORED_IMPLICIT_FALLTHROUGH
}

static void json_litex_expr_vm_init(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_vm_sizes_t* sizes)
{
    memset(vm, 0, sizeof(*vm));

    vm->mdata = pcre2_match_data_create(
        sizes->ovector_max,
        NULL);
    VERIFY(vm->mdata != NULL);

    vm->mctxt = pcre2_match_context_create(
        NULL);
    VERIFY(vm->mctxt != NULL);

    pcre2_set_match_limit(
        vm->mctxt, sizes->match_limit);
    pcre2_set_depth_limit(
        vm->mctxt, sizes->depth_limit);
    pcre2_set_heap_limit(
        vm->mctxt, sizes->heap_limit);

    json_litex_expr_vm_stack_init(
        &vm->stack,
        sizes->stack_max,
        sizes->stack_init);
}

static bool json_litex_expr_vm_done_codes(
    struct json_litex_expr_vm_t* vm);

static void json_litex_expr_vm_done(
    struct json_litex_expr_vm_t* vm)
{
    json_litex_expr_vm_done_codes(
        vm);
    json_litex_expr_vm_stack_done(
        &vm->stack);
    pcre2_match_context_free(
        vm->mctxt);
    pcre2_match_data_free(
        vm->mdata);
}

#define JSON_LITEX_EXPR_VM_CLEAR(n)       \
    do {                                  \
        memset(&vm->n, 0, sizeof(vm->n)); \
    } while (0)

static void json_litex_expr_vm_clear(
    struct json_litex_expr_vm_t* vm)
{
    JSON_LITEX_EXPR_VM_STACK_CLEAR();

    JSON_LITEX_EXPR_VM_CLEAR(error);
    JSON_LITEX_EXPR_VM_CLEAR(expr);
    JSON_LITEX_EXPR_VM_CLEAR(val);
    JSON_LITEX_EXPR_VM_CLEAR(ip);
}

enum json_litex_expr_inc_seq_cmp_op_t
{
    json_litex_expr_inc_seq_cmp_op_no,
    json_litex_expr_inc_seq_cmp_op_eq,
    json_litex_expr_inc_seq_cmp_op_ne,
    json_litex_expr_inc_seq_cmp_op_lt,
    json_litex_expr_inc_seq_cmp_op_le,
    json_litex_expr_inc_seq_cmp_op_gt,
    json_litex_expr_inc_seq_cmp_op_ge,
};

#undef  CASE
#define CASE(n) \
    case json_litex_expr_inc_seq_cmp_op_ ## n

static inline bool
    json_litex_expr_inc_seq_cmp(
        enum json_litex_expr_inc_seq_cmp_op_t op,
        json_litex_expr_value_t r,
        json_litex_expr_value_t n)
{
    switch (op) {

    CASE(no):
        return false;
    CASE(eq)://n == r
        return n <  r;
    CASE(ne)://n != r
        return n <  r;
    CASE(lt)://n <  r
        return n <  r;
    CASE(le)://n <= r
        return n <= r;
    CASE(gt)://n >  r
        return n <= r;
    CASE(ge)://n >= r
        return n <  r;

    default:
        UNEXPECT_VAR("%d", op);
    }
}

static json_litex_expr_value_t
    json_litex_expr_vm_count_str(
        enum json_litex_expr_inc_seq_cmp_op_t op,
        const uchar_t* str, const uchar_t* val,
        size_t len, json_litex_expr_value_t n)
{
    json_litex_expr_value_t r = 0;
    const uchar_t *p, *q, *e;
    size_t l;

    ASSERT(str != NULL);
    ASSERT(val != NULL);

    // stev: prefer using the C library
    // function 'strstr' over 'memmem'

    for (p = val,
         e = p + len,
         l = strulen(str);
         p < e;
         p = q + 1) {
        do {
            if (!(q = strustr(p, str)))
                break;
            r ++;
            if (json_litex_expr_inc_seq_cmp(
                    op, r, n))
                return r;

            p = q + l;
            ASSERT(p <= e);
        } while (p < e);

        if (!(q = memchr(p, 0, PTR_DIFF(e, p))))
            break;
    }

    return r;
}

// stev: 'end != NULL' indicates running
// the function below in a loop, thus not
// allowing empty matchings!

static bool json_litex_expr_vm_match_rex(
    struct json_litex_expr_vm_t* vm,
    json_litex_expr_rex_index_t rex,
    const uchar_t* val, size_t len,
    const uchar_t** end,
    bool* result)
{
    const pcre2_code* c;
    uint32_t o;
    int r;

    ASSERT(val != NULL);

    if (end != NULL && len == 0) {
        *result = false;
        *end = NULL;
        return true;
    }

    o = PCRE2_NO_UTF_CHECK;
    if (end) o |= PCRE2_NOTEMPTY;

    ASSERT(rex < vm->n_codes);
    c = vm->codes[rex];
    ASSERT(c != NULL);

    r = pcre2_match(
            c, val, len, 0, o,
            vm->mdata, vm->mctxt);
    *result = r > 0;

    if (end != NULL) {
        if (r <= 0)
            *end = NULL;
        else {
            const PCRE2_SIZE* o;

            ASSERT(pcre2_get_ovector_count(
                    vm->mdata) >= 1);
            o = pcre2_get_ovector_pointer(
                    vm->mdata);
            ASSERT(o != NULL);

            ASSERT(o[1] <= len);
            *end = val + o[1];
        }
    }

    if (r == PCRE2_ERROR_NOMATCH)
        return true;
    if (r == 0)
        return JSON_LITEX_EXPR_VM_PCRE2_ERROR(
            ovector, 0);
    if (r < 0)
        return JSON_LITEX_EXPR_VM_PCRE2_ERROR(
            match, r);

    return true;
}

static bool json_litex_expr_vm_count_rex(
    struct json_litex_expr_vm_t* vm,
    enum json_litex_expr_inc_seq_cmp_op_t op,
    json_litex_expr_rex_index_t rex,
    const struct json_litex_string_t* val,
    json_litex_expr_value_t n,
    json_litex_expr_value_t* result)
{
    const uchar_t *p, *e;
    bool m;

    ASSERT(val != NULL);

    p = val->ptr;
    e = p + val->len;
    *result = 0;

    while (p < e) {
        if (!json_litex_expr_vm_match_rex(
                vm, rex, p, PTR_DIFF(e, p), &p, &m))
            return false;

        if (!m ||
            json_litex_expr_inc_seq_cmp(
                op, *result, n))
            break;

        (*result) ++;
    }
    return true;
}

#undef  CASE
#define CASE(n) \
    case json_litex_expr_builtin_ ## n

static bool json_litex_expr_vm_exec_const_id(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    json_litex_expr_blt_node_t n;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, const_id);

    switch (n->type) {

    CASE(func):
        ASSERT(false);
        break;
    CASE(num):
        JSON_LITEX_EXPR_VM_STACK_PUSH(
            num, n->u.num.val);
        break;
    CASE(str):
        JSON_LITEX_EXPR_VM_STACK_PUSH(
            str, n->u.str.val);
        break;

    default:
        UNEXPECT_VAR("%d", n->type);
    }

    return true;
}

static bool json_litex_expr_vm_exec_const_num(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    json_litex_expr_num_node_t n;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, const_num);

    JSON_LITEX_EXPR_VM_STACK_PUSH(num, n);
    return true;
}

static bool json_litex_expr_vm_exec_const_str(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    json_litex_expr_str_node_t n;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, const_str);

    JSON_LITEX_EXPR_VM_STACK_PUSH(str, n);
    return true;
}

#define SEQ_CMP_OP(n) \
    json_litex_expr_inc_seq_cmp_op_ ## n
#define EXPR_CMP_OP(n) \
    json_litex_expr_cmp_op_ ## n

static void json_litex_expr_vm_get_op_bound(
    struct json_litex_expr_vm_t* vm,
    enum json_litex_expr_inc_seq_cmp_op_t* op,
    json_litex_expr_value_t* val)
{
    enum json_litex_expr_inc_seq_cmp_op_t o;
    const struct json_litex_expr_node_t* n;
    json_litex_expr_value_t v;
    size_t i = vm->ip, l;

    STATIC(
        SEQ_CMP_OP(no) == 0 &&
        SEQ_CMP_OP(eq) == EXPR_CMP_OP(eq) + 1 &&
        SEQ_CMP_OP(ne) == EXPR_CMP_OP(ne) + 1 &&
        SEQ_CMP_OP(lt) == EXPR_CMP_OP(lt) + 1 &&
        SEQ_CMP_OP(le) == EXPR_CMP_OP(le) + 1 &&
        SEQ_CMP_OP(gt) == EXPR_CMP_OP(gt) + 1 &&
        SEQ_CMP_OP(ge) == EXPR_CMP_OP(ge) + 1);

    ASSERT(vm->expr->size > 0);
    l = vm->expr->size - 1;

    ASSERT(i <= l);

    // stev: determine if the instruction
    // sequence matches one the following
    // forms ('@' marks 'ip's position):
    //   @, EOS         #`a` (i.e. @ is at EOS)
    //   @, cmp_op      #`a`>=1 or 1<=#`a`
    //   @, not_op      ! #`a`
    //   @, make_bool   #`a`||#`b` or #`a`&&#`b`
    //   @, jump_false  #`a`&&#`b`
    //   @, jump_true   #`a`||#`b`

    // stev: current instruction is the last one
#ifndef JSON_LITEX_TEST_EXPR
    if (i == l) goto bool_op;
#else
    if (i == l) goto no_op;
#endif // JSON_LITEX_TEST_EXPR

    // stev: i < l => i + 1 <= l
    n = &vm->expr->nodes[i + 1];

    if (JSON_LITEX_EXPR_NODE_IS_CONST(n, cmp_op)) {
        v = JSON_LITEX_EXPR_VM_STACK_TOP_AS(num);
        o = n->node.cmp_op + 1;
    }
    else
    if (JSON_LITEX_EXPR_NODE_IS_CONST(n, not_op) ||
        JSON_LITEX_EXPR_NODE_IS_CONST(n, make_bool) ||
        JSON_LITEX_EXPR_NODE_IS_CONST(n, jump_false) ||
        JSON_LITEX_EXPR_NODE_IS_CONST(n, jump_true)) {
#ifndef JSON_LITEX_TEST_EXPR
    bool_op:
#endif // JSON_LITEX_TEST_EXPR
        o = SEQ_CMP_OP(ge);
        v = 1;
    }
    else {
#ifdef JSON_LITEX_TEST_EXPR
    no_op:
#endif // JSON_LITEX_TEST_EXPR
        o = SEQ_CMP_OP(no);
        v = 0;
    }

    *val = v;
    *op = o;
}

#undef EXPR_CMP_OP
#undef SEQ_CMP_OP

static bool json_litex_expr_vm_exec_count_str(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    enum json_litex_expr_inc_seq_cmp_op_t o;
    const struct json_litex_string_t* v;
    json_litex_expr_str_node_t n;
    json_litex_expr_value_t r, m;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, count_str);
    v = JSON_LITEX_VALUE_AS_IF_STRING(
            vm->val);

    if ((r = (v != NULL))) {
        json_litex_expr_vm_get_op_bound(
                vm, &o, &m);
        r = json_litex_expr_vm_count_str(
                o, n, v->ptr,
                v->len, m);
    }

    JSON_LITEX_EXPR_VM_STACK_PUSH(num, r);
    return true;
}

static bool json_litex_expr_vm_exec_count_rex(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    enum json_litex_expr_inc_seq_cmp_op_t o;
    const struct json_litex_string_t* v;
    json_litex_expr_rex_node_t n;
    json_litex_expr_value_t r, m;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, count_rex);
    v = JSON_LITEX_VALUE_AS_IF_STRING(
            vm->val);

    if ((r = (v != NULL))) {
        json_litex_expr_vm_get_op_bound(
                vm, &o, &m);
        if (!json_litex_expr_vm_count_rex(
                vm, o, n, v, m, &r))
            return false;
    }

    JSON_LITEX_EXPR_VM_STACK_PUSH(num, r);
    return true;
}

static bool json_litex_expr_vm_exec_match_str(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    const struct json_litex_string_t* v;
    json_litex_expr_str_node_t n;
    bool r;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, match_str);
    v = JSON_LITEX_VALUE_AS_IF_STRING(
            vm->val);

    r = v != NULL &&
        strlucmp(n, v->ptr, v->len);

    JSON_LITEX_EXPR_VM_STACK_PUSH(num, r);
    return true;
}

static bool json_litex_expr_vm_exec_match_rex(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    const struct json_litex_string_t* v;
    json_litex_expr_rex_node_t n;
    bool r;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, match_rex);
    v = JSON_LITEX_VALUE_AS_IF_STRING(
            vm->val);

    if ((r = v != NULL) &&
        !json_litex_expr_vm_match_rex(
            vm, n, v->ptr, v->len,
            NULL, &r))
        return false;

    JSON_LITEX_EXPR_VM_STACK_PUSH(num, r);
    return true;
}

static bool json_litex_expr_vm_exec_call_builtin(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    const struct json_litex_expr_builtin_func_t* f;
    const struct json_litex_expr_builtin_t* n;
    json_litex_expr_value_t v;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, call_builtin);
    f = JSON_LITEX_EXPR_BUILTIN_AS_CONST(
            n, func);

    if (!f->val(vm, vm->val, &v))
        return false;

    if (f->n_args > 0)
        JSON_LITEX_EXPR_VM_STACK_POP_N(
            f->n_args - 1);

    JSON_LITEX_EXPR_VM_STACK_PUSH(num, v);
    return true;
}

static bool json_litex_expr_vm_exec_jump_false(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    json_litex_expr_jmp_node_t n;
    json_litex_expr_value_t* t;
    size_t k;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, jump_false);

    k = UINT_TO_SIZE(n.offs);
    ASSERT_SIZE_ADD_NO_OVERFLOW(vm->ip, k);
    ASSERT(vm->ip + k <= vm->expr->size);
    ASSERT(k > 0);

    t = JSON_LITEX_EXPR_VM_STACK_TOP_REF_AS(num);
    if (!*t)
        vm->ip += k - 1;

    if (*t || n.popu)
        JSON_LITEX_EXPR_VM_STACK_POP();
    else
        *t = !!*t;

    return true;
}

static bool json_litex_expr_vm_exec_jump_true(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    json_litex_expr_jmp_node_t n;
    json_litex_expr_value_t* t;
    size_t k;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, jump_true);

    k = UINT_TO_SIZE(n.offs);
    ASSERT_SIZE_ADD_NO_OVERFLOW(vm->ip, k);
    ASSERT(vm->ip + k <= vm->expr->size);
    ASSERT(k > 0);

    t = JSON_LITEX_EXPR_VM_STACK_TOP_REF_AS(num);
    if (*t)
        vm->ip += k - 1;

    if (!*t || n.popu)
        JSON_LITEX_EXPR_VM_STACK_POP();
    else
        *t = !!*t;

    return true;
}

static bool json_litex_expr_vm_exec_make_bool(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    json_litex_expr_value_t* t;

    ASSERT(JSON_LITEX_EXPR_NODE_IS_CONST(
        self, make_bool));

    t = JSON_LITEX_EXPR_VM_STACK_TOP_REF_AS(num);
    *t = !!*t;

    return true;
}

#undef  CASE
#define CASE(n, o)                     \
    case json_litex_expr_cmp_op_ ## n: \
        *v0 = *v0 o v1;                \
        break

static bool json_litex_expr_vm_exec_cmp_op(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    json_litex_expr_value_t *v0, v1;
    json_litex_expr_cmp_node_t n;

    n = JSON_LITEX_EXPR_NODE_AS_CONST(
            self, cmp_op);

    v1 = JSON_LITEX_EXPR_VM_STACK_POP_AS(num);
    v0 = JSON_LITEX_EXPR_VM_STACK_TOP_REF_AS(num);

    switch (n) {
    CASE(eq, ==);
    CASE(ne, !=);
    CASE(lt, <);
    CASE(le, <=);
    CASE(gt, >);
    CASE(ge, >=);
    default:
        UNEXPECT_VAR("%d", n);
    }

    return true;
}

static bool json_litex_expr_vm_exec_not_op(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_node_t* self)
{
    json_litex_expr_value_t* t;

    ASSERT(JSON_LITEX_EXPR_NODE_IS_CONST(
        self, not_op));

    t = JSON_LITEX_EXPR_VM_STACK_TOP_REF_AS(num);
    *t = !*t;

    return true;
}

static bool json_litex_expr_vm_init_codes(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_rex_def_t* rexes)
{
    union json_litex_expr_rex_codes_pack_t p;

    vm->n_codes = rexes != NULL
        ? rexes->size : 0;

    if (vm->n_codes == 0) {
        vm->free_codes = false;
        vm->codes = NULL;
        return true;
    }

    if ((p.patterns =
            JSON_LITEX_EXPR_REX_CODES_AS_IF_CONST(
                &rexes->codes, patterns))) {
        const pcre2_code *const*q, *const*e;

        ASSERT(p.patterns->size == rexes->size);

        for (q = p.patterns->objs,
             e = q + p.patterns->size;
             q < e;
             q ++)
             ASSERT(*q != NULL);

        vm->codes = CONST_PTR_CONST_PTR_CAST(
            p.patterns->objs, pcre2_code);
        vm->free_codes = false;
    }
    else
    if ((p.streams =
            JSON_LITEX_EXPR_REX_CODES_AS_IF_CONST(
                &rexes->codes, streams))) {
        int32_t r;

        vm->codes = malloc(
            SIZE_MUL(vm->n_codes, sizeof(*vm->codes)));
        VERIFY(vm->codes != NULL);
        vm->free_codes = true;

        if ((r = pcre2_serialize_decode(
                    vm->codes,
                    vm->n_codes,
                    p.streams->bytes,
                    NULL)) < 0)
            return JSON_LITEX_EXPR_VM_PCRE2_ERROR(
                decode, r);

        ASSERT(INT_AS_SIZE(r) == vm->n_codes);
    }
    else
        UNEXPECT_VAR("%d", rexes->codes.type);

    return true;
}

static bool json_litex_expr_vm_done_codes(
    struct json_litex_expr_vm_t* vm)
{
    pcre2_code **p, **e;

    if (vm->n_codes == 0 ||
        !vm->free_codes)
        return true;

    ASSERT(vm->codes != NULL);

    for (p = vm->codes,
         e = p + vm->n_codes;
         p < e;
         p ++)
         pcre2_code_free(*p);

    free(vm->codes);
    vm->free_codes = false;
    vm->codes = NULL;
    vm->n_codes = 0;

    return true;
}

static bool json_litex_expr_vm_set_rexes(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_expr_rex_def_t* rexes)
{
    return
        json_litex_expr_vm_done_codes(vm) &&
        json_litex_expr_vm_init_codes(vm, rexes);
}

#undef  CASE
#define CASE(n)                    \
    [json_litex_expr_node_ ## n] = \
     json_litex_expr_vm_exec_ ## n

static bool json_litex_expr_vm_execute(
    struct json_litex_expr_vm_t* vm,
#ifdef JSON_LITEX_TEST_EXPR
    const struct json_litex_expr_vm_alpha_t* alpha,
#endif
    const struct json_litex_expr_def_t* expr,
    const struct json_litex_value_t* value,
    json_litex_expr_value_t* result)
{
    typedef bool (*json_litex_expr_vm_ins_t)(
        struct json_litex_expr_vm_t*,
        const struct json_litex_expr_node_t*);
    static const json_litex_expr_vm_ins_t inss[] = {
        CASE(const_id),
        CASE(const_num),
        CASE(const_str),
        CASE(count_str),
        CASE(count_rex),
        CASE(match_str),
        CASE(match_rex),
        CASE(call_builtin),
        CASE(jump_false),
        CASE(jump_true),
        CASE(make_bool),
        CASE(cmp_op),
        CASE(not_op),
    };
    const struct json_litex_expr_node_t* n;
    struct json_litex_expr_obj_t* o;
    json_litex_expr_vm_ins_t f;
    size_t s;

    // stev: cannot execute an expression if
    // the earlier attempt at decoding rexes
    // bytecode streams has failed
    if (JSON_LITEX_EXPR_VM_ERROR_IS(pcre2_decode))
        return false;

    json_litex_expr_vm_clear(vm);

#ifdef JSON_LITEX_TEST_EXPR
    vm->alpha = *alpha;
#endif
    vm->expr = expr;
    vm->val = value;

    while (vm->ip < vm->expr->size) {
        n = &vm->expr->nodes[vm->ip];

        if (!(f = ARRAY_NULL_ELEM(inss, n->type)))
            UNEXPECT_VAR("%d", n->type);

        if (!f(vm, n))
            break;

        vm->ip ++;
    }

    if (!JSON_LITEX_EXPR_VM_ERROR_IS(none))
        return false;

    s = JSON_LITEX_EXPR_VM_STACK_SIZE();
    ENSURE(s == 1, "invalid expr-vm stack: "
        "size is %zu", s);

    o = JSON_LITEX_EXPR_VM_STACK_TOP_REF();
    *result = JSON_LITEX_EXPR_OBJ_AS(o, num);

    JSON_LITEX_EXPR_VM_STACK_POP();

    return true;
}

static const struct json_litex_expr_obj_t*
    json_litex_expr_vm_get_arg(
        struct json_litex_expr_vm_t* vm,
        const struct json_litex_expr_builtin_t* def,
        size_t arg)
{
    const struct json_litex_expr_builtin_func_t* f;
    const struct json_litex_expr_builtin_t* d;
    const struct json_litex_expr_node_t* n;

    ASSERT(vm->ip < vm->expr->size);
    n = &vm->expr->nodes[vm->ip];

    d = JSON_LITEX_EXPR_NODE_AS_CONST(n, call_builtin);
    ASSERT(d == def);

    f = JSON_LITEX_EXPR_BUILTIN_AS_CONST(d, func);
    ASSERT(arg < f->n_args);

    // k := n_args - arg - 1:
    // arg == 0 => k == n_args - 1
    // arg == n_args - 1 => k == 0
    return STACK_TOP_N_REF(
        &vm->stack, f->n_args - arg - 1);
}

#ifdef JSON_LITEX_TEST_EXPR

static bool json_litex_expr_alpha_builtin_func(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_value_t* val UNUSED,
    json_litex_expr_value_t* result)
{
    const struct json_litex_expr_builtin_func_t* f;
    const struct json_litex_expr_builtin_t* d;
    const struct json_litex_expr_node_t* n;
    size_t k, l, v;
    uchar_t c;

    ASSERT(vm->ip < vm->expr->size);
    n = &vm->expr->nodes[vm->ip];

    d = JSON_LITEX_EXPR_NODE_AS_CONST(
            n, call_builtin);
    f = JSON_LITEX_EXPR_BUILTIN_AS_CONST(
            d, func);
    l = strulen(d->name);

    ASSERT(l >= 1);
    ASSERT(l <= 2);

    c = d->name[0];
    ASSERT(ISALPHA(c));

    if (l == 2) {
        ASSERT(ISUPPER(c));
        *result = 0;
    }
    else {
        k = c - (ISUPPER(c) ? 'A' : 'a');

        ASSERT(ARRAY_INDEX(vm->alpha.vals, k));
        v = vm->alpha.vals[k];

        if (f->is_bool) v = !!v;
        *result = v;
    }
    return true;
}

#endif // JSON_LITEX_TEST_EXPR

#define JSON_LITEX_EXPR_VM_GET_ARG(n, k)  \
    (                                     \
        json_litex_expr_vm_get_arg(       \
            vm, &json_litex_expr_ ## n ## \
            _builtin, k)                  \
    )

#undef  CASE
#define CASE(n) \
    case json_litex_value_ ## n ## _type

static bool json_litex_expr_builtin_func_null(
    struct json_litex_expr_vm_t* vm UNUSED,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result)
{
    switch (val->type) {
    CASE(null):
        *result = 1;
        return true;
    CASE(boolean):
    CASE(number):
    CASE(string):
        *result = 0;
        return true;
    default:
        UNEXPECT_VAR("%d", val->type);
    }
}

static bool json_litex_expr_builtin_func_boolean(
    struct json_litex_expr_vm_t* vm UNUSED,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result)
{
    switch (val->type) {
    CASE(boolean):
        *result = 1;
        return true;
    CASE(null):
    CASE(number):
    CASE(string):
        *result = 0;
        return true;
    default:
        UNEXPECT_VAR("%d", val->type);
    }
}

static bool json_litex_expr_builtin_func_number(
    struct json_litex_expr_vm_t* vm UNUSED,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result)
{
    switch (val->type) {
    CASE(number):
        *result = 1;
        return true;
    CASE(null):
    CASE(boolean):
    CASE(string):
        *result = 0;
        return true;
    default:
        UNEXPECT_VAR("%d", val->type);
    }
}

static bool json_litex_expr_builtin_func_string(
    struct json_litex_expr_vm_t* vm UNUSED,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result)
{
    switch (val->type) {
    CASE(string):
        *result = 1;
        return true;
    CASE(null):
    CASE(boolean):
    CASE(number):
        *result = 0;
        return true;
    default:
        UNEXPECT_VAR("%d", val->type);
    }
}

static bool json_litex_expr_builtin_func_len(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result)
{
    STATIC(
        offsetof(struct json_litex_value_t,
            u.number) ==
        offsetof(struct json_litex_value_t,
            u.string));

    switch (val->type) {
    CASE(null):
        return JSON_LITEX_EXPR_VM_ERROR(
            len_of_null);
    CASE(boolean):
        return JSON_LITEX_EXPR_VM_ERROR(
            len_of_bool);
    CASE(number):
    CASE(string):
        *result = val->u.string.len;
        return true;
    default:
        UNEXPECT_VAR("%d", val->type);
    }
}

#if INT64_MAX == LONG_MAX
#define strtoint64_ strtol
#elif UINT64_MAX == ULLONG_MAX
#define strtoint64_ strtoll
#else
#error int64_t is neither \
    signed long nor \
    signed long long
#endif
#define strtoint64(s, e)         \
    (                            \
        ISDIGIT(*s) || *s == '-' \
        ? strtoint64_(s, e, 10)  \
        : (errno = EINVAL, 0)    \
    )

#if UINT64_MAX == ULONG_MAX
#define strtouint64_ strtoul
#elif UINT64_MAX == ULLONG_MAX
#define strtouint64_ strtoull
#else
#error uint64_t is neither \
    unsigned long nor \
    unsigned long long
#endif
#define strtouint64(s, e)        \
    (                            \
        ISDIGIT(*s)              \
        ? strtouint64_(s, e, 10) \
        : (errno = EINVAL, 0)    \
    )

#define JSON_LITEX_EXPR_PARSE_INT64(t, s, r) \
    ({                                       \
        char* __p = NULL;                    \
        STATIC(TYPEOF_IS(r, t ## 64_t));     \
        errno = 0;                           \
        ASSERT(*s);                          \
        r = strto ## t ## 64(                \
                PTR_CHAR_CAST_CONST(s),      \
                &__p);                       \
        errno == 0 && *__p == 0;             \
    })
#define JSON_LITEX_EXPR_CHECK_INT__(n, v) \
    (                                     \
        STATIC(TYPEOF_IS(v, int64_t)),    \
        (v) >= INT ## n ## _MIN &&        \
        (v) <= INT ## n ## _MAX           \
    )
#define JSON_LITEX_EXPR_CHECK_UINT__(n, v) \
    (                                      \
        STATIC(TYPEOF_IS(v, uint64_t)),    \
        (v) <= UINT ## n ## _MAX           \
    )
#define JSON_LITEX_EXPR_CHECK_INT_(c, n, v) \
        JSON_LITEX_EXPR_CHECK_ ## c ## INT__(n, v)

#define JSON_LITEX_EXPR_CHECK_INT(c, t, n, s)     \
    ({                                            \
        bool __r;                                 \
        t ## 64_t __v;                            \
        if ((__r = (s.len == strulen(s.ptr) &&    \
                    JSON_LITEX_EXPR_PARSE_INT64(  \
                        t, s.ptr, __v)))) {       \
            switch (n) {                          \
            case 8:                               \
                __r = JSON_LITEX_EXPR_CHECK_INT_( \
                        c, 8, __v);               \
                break;                            \
            case 16:                              \
                __r = JSON_LITEX_EXPR_CHECK_INT_( \
                        c, 16, __v);              \
                break;                            \
            case 32:                              \
                __r = JSON_LITEX_EXPR_CHECK_INT_( \
                        c, 32, __v);              \
                break;                            \
            }                                     \
        }                                         \
        __r;                                      \
    })

#define JSON_LITEX_EXPR_BUILTIN_FUNC_INT_(c, t, n) \
    ({                                             \
        STATIC(                                    \
            offsetof(struct json_litex_value_t,    \
                u.number) ==                       \
            offsetof(struct json_litex_value_t,    \
                u.string));                        \
        if (n != 8 &&                              \
            n != 16 &&                             \
            n != 32 &&                             \
            n != 64)                               \
            return JSON_LITEX_EXPR_VM_ERROR(       \
                invalid_ ## t ## _arg);            \
        switch (val->type) {                       \
        CASE(null):                                \
        CASE(boolean):                             \
            *result = 0;                           \
            break;                                 \
        CASE(number):                              \
        CASE(string):                              \
            *result = JSON_LITEX_EXPR_CHECK_INT(   \
                c, t, n, val->u.string);           \
            break;                                 \
        default:                                   \
            UNEXPECT_VAR("%d", val->type);         \
        }                                          \
        true;                                      \
    })
#define JSON_LITEX_EXPR_BUILTIN_FUNC_UINT(n) \
        JSON_LITEX_EXPR_BUILTIN_FUNC_INT_(U, uint, n)
#define JSON_LITEX_EXPR_BUILTIN_FUNC_INT(n) \
        JSON_LITEX_EXPR_BUILTIN_FUNC_INT_(, int, n)

static bool json_litex_expr_builtin_func_int(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result)
{
    const struct json_litex_expr_obj_t* a;

    a = JSON_LITEX_EXPR_VM_GET_ARG(int, 0);
    ASSERT(JSON_LITEX_EXPR_OBJ_IS_CONST(a, num));

    return JSON_LITEX_EXPR_BUILTIN_FUNC_INT(a->num);
}

static bool json_litex_expr_builtin_func_uint(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result)
{
    const struct json_litex_expr_obj_t* a;

    a = JSON_LITEX_EXPR_VM_GET_ARG(uint, 0);
    ASSERT(JSON_LITEX_EXPR_OBJ_IS_CONST(a, num));

    return JSON_LITEX_EXPR_BUILTIN_FUNC_UINT(a->num);
}

// $ adjust-func() { ssed -R '1s/^const\s+char\s*\*\s*([a-z0-9_]+\()/static const char*\n\t\1\n\t\t/'; }
// $ cut -d $'\t' -f1 time-zones.txt|gen-func -f json_litex_expr_parse_time_zone -r- -m-|adjust-func

static const char*
    json_litex_expr_parse_time_zone(
        const char* p)
{
    switch (*p ++) {
    case 'A':
        switch (*p ++) {
        case 'D':
            if (*p ++ == 'T')
                return p; // "ADT"
            return NULL;
        case 'K':
            switch (*p ++) {
            case 'D':
                if (*p ++ == 'T')
                    return p; // "AKDT"
                return NULL;
            case 'S':
                if (*p ++ == 'T')
                    return p; // "AKST"
            }
            return NULL;
        case 'R':
            if (*p ++ == 'T')
                return p; // "ART"
            return NULL;
        case 'S':
            if (*p ++ == 'T')
                return p; // "AST"
        }
        return NULL;
    case 'B':
        switch (*p ++) {
        case 'R':
            switch (*p ++) {
            case 'S':
                if (*p ++ == 'T')
                    return p; // "BRST"
                return NULL;
            case 'T':
                return p; // "BRT"
            }
            return NULL;
        case 'S':
            if (*p ++ == 'T')
                return p; // "BST"
        }
        return NULL;
    case 'C':
        switch (*p ++) {
        case 'A':
            if (*p ++ == 'T')
                return p; // "CAT"
            return NULL;
        case 'D':
            if (*p ++ == 'T')
                return p; // "CDT"
            return NULL;
        case 'E':
            switch (*p ++) {
            case 'S':
                if (*p ++ == 'T')
                    return p; // "CEST"
                return NULL;
            case 'T':
                return p; // "CET"
            }
            return NULL;
        case 'L':
            switch (*p ++) {
            case 'S':
                if (*p ++ == 'T')
                    return p; // "CLST"
                return NULL;
            case 'T':
                return p; // "CLT"
            }
            return NULL;
        case 'S':
            if (*p ++ == 'T')
                return p; // "CST"
        }
        return NULL;
    case 'E':
        switch (*p ++) {
        case 'A':
            if (*p ++ == 'T')
                return p; // "EAT"
            return NULL;
        case 'D':
            if (*p ++ == 'T')
                return p; // "EDT"
            return NULL;
        case 'E':
            switch (*p ++) {
            case 'S':
                if (*p ++ == 'T')
                    return p; // "EEST"
                return NULL;
            case 'T':
                return p; // "EET"
            }
            return NULL;
        case 'S':
            if (*p ++ == 'T')
                return p; // "EST"
        }
        return NULL;
    case 'G':
        if (*p ++ == 'S' &&
            *p ++ == 'T')
            return p; // "GST"
        return NULL;
    case 'H':
        switch (*p ++) {
        case 'A':
            switch (*p ++) {
            case 'D':
                if (*p ++ == 'T')
                    return p; // "HADT"
                return NULL;
            case 'S':
                if (*p ++ == 'T')
                    return p; // "HAST"
            }
            return NULL;
        case 'S':
            if (*p ++ == 'T')
                return p; // "HST"
        }
        return NULL;
    case 'I':
        if (*p ++ == 'S' &&
            *p ++ == 'T')
            return p; // "IST"
        return NULL;
    case 'J':
        if (*p ++ == 'S' &&
            *p ++ == 'T')
            return p; // "JST"
        return NULL;
    case 'K':
        if (*p ++ == 'S' &&
            *p ++ == 'T')
            return p; // "KST"
        return NULL;
    case 'M':
        switch (*p ++) {
        case 'D':
            if (*p ++ == 'T')
                return p; // "MDT"
            return NULL;
        case 'E':
            switch (*p ++) {
            case 'S':
                switch (*p ++) {
                case 'T':
                    return p; // "MEST"
                case 'Z':
                    return p; // "MESZ"
                }
                return NULL;
            case 'T':
                return p; // "MET"
            case 'Z':
                return p; // "MEZ"
            }
            return NULL;
        case 'S':
            switch (*p ++) {
            case 'D':
                return p; // "MSD"
            case 'K':
                return p; // "MSK"
            case 'T':
                return p; // "MST"
            }
        }
        return NULL;
    case 'N':
        switch (*p ++) {
        case 'D':
            if (*p ++ == 'T')
                return p; // "NDT"
            return NULL;
        case 'S':
            if (*p ++ == 'T')
                return p; // "NST"
            return NULL;
        case 'Z':
            switch (*p ++) {
            case 'D':
                if (*p ++ == 'T')
                    return p; // "NZDT"
                return NULL;
            case 'S':
                if (*p ++ == 'T')
                    return p; // "NZST"
            }
        }
        return NULL;
    case 'P':
        switch (*p ++) {
        case 'D':
            if (*p ++ == 'T')
                return p; // "PDT"
            return NULL;
        case 'S':
            if (*p ++ == 'T')
                return p; // "PST"
        }
        return NULL;
    case 'S':
        switch (*p ++) {
        case 'A':
            if (*p ++ == 'S' &&
                *p ++ == 'T')
                return p; // "SAST"
            return NULL;
        case 'G':
            if (*p ++ == 'T')
                return p; // "SGT"
            return NULL;
        case 'S':
            if (*p ++ == 'T')
                return p; // "SST"
        }
        return NULL;
    case 'U':
        if (*p ++ == 'T' &&
            *p ++ == 'C')
            return p; // "UTC"
        return NULL;
    case 'W':
        switch (*p ++) {
        case 'A':
            if (*p ++ == 'T')
                return p; // "WAT"
            return NULL;
        case 'E':
            switch (*p ++) {
            case 'S':
                if (*p ++ == 'T')
                    return p; // "WEST"
                return NULL;
            case 'T':
                return p; // "WET"
            }
        }
    }
    return NULL;
}

// stev: the function 'json_litex_expr_parse_date'
// augments the library function 'strptime' adding
// the following specifiers: %N, %z and %Z

static bool json_litex_expr_parse_date(
    const char* str, const char* fmt)
{
    const char *p = str, *f = fmt;
    enum { N = 128 };
    char b[N + 1];

    for (; *p && *f; fmt = f) {
        size_t n;

        for (n = 0;
             n < N && *f;
             n ++,
             f ++) {
            if (*f != '%')
                continue;

            // => *f == '%'
            if (f[1] == 'N' ||
                f[1] == 'z' ||
                f[1] == 'Z')
                break;

            if (n == N - 1)
                break;
            //  => n < N - 1
            // <=> n + 1 < N
            ++ n;
            ++ f;
        }
        ASSERT(n <= N);

        if (n > 0) {
            struct tm t;

            memcpy(b, fmt, n);
            b[n] = 0;

            memset(&t, 0, sizeof(t));
            if (!(p = strptime(p, b, &t)))
                return false;
        }

        if (*f == 0)
            break;
        if (*f != '%')
            continue;

        ++ f;
        switch (*f ++) {

        case 'N': {
            uint64_t v;
            char* q;

            errno = 0;
            v = strtouint64(p, &q);
            if (errno || v > 999999999)
                return false;

            ASSERT(q > p);
            p = q;
            continue;
        }

        case 'z': {
            size_t n = 0;
            // stev: perform as newer versions of
            // 'strptime' do, accepting: '[-+]HH',
            // '[-+]HHMM', '[-+]HH:MM' or 'Z'
            while (ISSPACE(*p))
                ++ p;
            if (*p == 'Z') {
                ++ p;
                continue;
            }
            if (*p != '-' && *p != '+')
                return false;

            ++ p;
            while (n < 4 && ISDIGIT(*p)) {
                if (*++ p == ':' && n == 1) {
                    if (!ISDIGIT(p[1]))
                        return false;
                    ++ p;
                }
                ++ n;
            }
            if (n != 2 && n != 4)
                return false;

            continue;
        }

        case 'Z': {
            if (!(p = json_litex_expr_parse_time_zone(p)))
                return false;

            continue;
        }

        default:
            // stev: not '%[NzZ]', thus let
            // this specifier be processed
            // by the next 'strptime' call
            f -= 2;
        }
    }

    return !*p && !*f;
}

static bool json_litex_expr_builtin_func_date(
    struct json_litex_expr_vm_t* vm,
    const struct json_litex_value_t* val,
    json_litex_expr_value_t* result)
{
    const struct json_litex_expr_obj_t* a;
    const struct json_litex_string_t* v;

    v = JSON_LITEX_VALUE_AS_IF_STRING(val);

    // stev: reject strings embedding NUL chars
    if (v == NULL || v->len != strulen(v->ptr)) {
        *result = 0;
        return true;
    }

    a = JSON_LITEX_EXPR_VM_GET_ARG(date, 0);
    ASSERT(JSON_LITEX_EXPR_OBJ_IS_CONST(a, str));

    *result = json_litex_expr_parse_date(
        PTR_CHAR_CAST_CONST(v->ptr),
        PTR_CHAR_CAST_CONST(a->str));

    return true;
}

#define JSON_LITEX_EXPR_BUILTIN_DEF_(t, n, m, ...) \
JSON_API const struct json_litex_expr_builtin_t    \
    json_litex_expr_ ## n ## _builtin = {          \
        .name = (const uchar_t*) #n,               \
        .type = json_litex_expr_builtin_ ## t,     \
        .u.t = m(__VA_ARGS__)                      \
    };                                             \

#define JSON_LITEX_EXPR_BUILTIN_FUNC_DEF_(f, b, n, a) \
    {                                                 \
        .val = json_litex_expr_builtin_func_ ## f,    \
        .is_bool = b,                                 \
        .n_args = n,                                  \
        .args = a                                     \
    }
#define JSON_LITEX_EXPR_BUILTIN_NUM_DEF_(v) \
    { .val = v }
#define JSON_LITEX_EXPR_BUILTIN_STR_DEF_(v) \
    { .val = (const uchar_t*) v }

#define JSON_LITEX_EXPR_BUILTIN_FUNC_ARG(n, a)      \
    (                                               \
        json_litex_expr_builtin_func_arg_ ## a << n \
    )
#define JSON_LITEX_EXPR_BUILTIN_FUNC_ARGS(...) \
    (                                          \
        VA_ARGS_REPEAT_N_EMPTY(0, |,           \
            JSON_LITEX_EXPR_BUILTIN_FUNC_ARG,  \
            ## __VA_ARGS__)                    \
    )
#define JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(f, b, ...) \
        JSON_LITEX_EXPR_BUILTIN_DEF_(func, f,       \
            JSON_LITEX_EXPR_BUILTIN_FUNC_DEF_,      \
            f, b, VA_ARGS_NARGS(__VA_ARGS__),       \
            JSON_LITEX_EXPR_BUILTIN_FUNC_ARGS(      \
                __VA_ARGS__))

#define JSON_LITEX_EXPR_BUILTIN_NUM_DEF(n, v)    \
        JSON_LITEX_EXPR_BUILTIN_DEF_(num, n,     \
            JSON_LITEX_EXPR_BUILTIN_NUM_DEF_, v)
#define JSON_LITEX_EXPR_BUILTIN_STR_DEF(n, v)    \
        JSON_LITEX_EXPR_BUILTIN_DEF_(str, n,     \
            JSON_LITEX_EXPR_BUILTIN_STR_DEF_, v)

JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(null, true);
JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(boolean, true);
JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(number, true);
JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(string, true);

JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(len, false);
JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(int,  true, num);
JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(uint, true, num);
JSON_LITEX_EXPR_BUILTIN_FUNC_DEF(date, true, str);

JSON_LITEX_EXPR_BUILTIN_STR_DEF(timestamp, "%s");
JSON_LITEX_EXPR_BUILTIN_STR_DEF(long_iso, "%Y-%m-%d %H:%M:%S");
JSON_LITEX_EXPR_BUILTIN_STR_DEF(iso_8601, "%Y-%m-%dT%H:%M:%S.%NZ");
JSON_LITEX_EXPR_BUILTIN_STR_DEF(full_iso, "%Y-%m-%d %H:%M:%S.%N %z");
JSON_LITEX_EXPR_BUILTIN_STR_DEF(c_locale, "%a %b %e %H:%M:%S %Z %Y");

#ifndef JSON_LITEX_TEST_EXPR

#undef  PRINT_DEBUG_COND
#define PRINT_DEBUG_COND ruler->debug

typedef void (*json_litex_ruler_string_rule_t)(void*,
    const struct json_text_pos_t*,
    const uchar_t*, const uchar_t*);
typedef void (*json_litex_ruler_size_rule_t)(void*,
    const struct json_text_pos_t*,
    size_t);

struct json_litex_ruler_rules_t
{
    json_litex_ruler_string_rule_t str_val_rule;
    json_litex_ruler_string_rule_t obj_key_rule;
    json_litex_ruler_size_rule_t   obj_val_rule;
    json_litex_ruler_size_rule_t   arr_val_rule;
};

struct json_litex_ruler_t
{
    const struct json_litex_ruler_rules_t*
          rules;
    void* rules_obj;
};

static void json_litex_ruler_init(
    struct json_litex_ruler_t* ruler,
    const struct json_litex_ruler_rules_t* rules,
    void* rules_obj)
{
    memset(ruler, 0, sizeof(struct json_litex_ruler_t));

    ruler->rules = rules;
    ruler->rules_obj = rules_obj;
}

static void json_litex_ruler_done(
    struct json_litex_ruler_t* ruler UNUSED)
{
    // stev: nop
}

enum json_litex_lib_error_t
{
    json_litex_lib_error_none,
    json_litex_lib_error_sys,
    json_litex_lib_error_lib,
    json_litex_lib_error_ast,
    json_litex_lib_error_meta,
    json_litex_lib_error_attr,
    json_litex_lib_error_expr
};

enum json_litex_lib_error_sys_context_t
{
    json_litex_lib_error_file_open_context,
    json_litex_lib_error_file_stat_context,
    json_litex_lib_error_file_read_context,
    json_litex_lib_error_file_close_context
};

struct json_litex_lib_error_sys_t
{
    enum json_litex_lib_error_sys_context_t context;
    int error;
};

enum json_litex_lib_generic_lib_error_type_t
{
    json_litex_lib_generic_lib_error_path_name_not_available,
    json_litex_lib_generic_lib_error_path_name_not_found,
};

enum json_litex_lib_shared_lib_error_type_t
{
    json_litex_lib_shared_lib_error_invalid_name,
    json_litex_lib_shared_lib_error_load_lib_failed,
    json_litex_lib_shared_lib_error_get_version_sym_not_found,
    json_litex_lib_shared_lib_error_get_def_sym_not_found,
    json_litex_lib_shared_lib_error_wrong_lib_version,
    json_litex_lib_shared_lib_error_def_is_null,
    json_litex_lib_shared_lib_error_def_not_valid,
};

enum json_litex_lib_error_lib_type_t
{
    json_litex_lib_error_generic_lib,
    json_litex_lib_error_shared_lib,
};

struct json_litex_lib_error_lib_t
{
    enum json_litex_lib_error_lib_type_t type;
    union {
        enum json_litex_lib_generic_lib_error_type_t generic;
        enum json_litex_lib_shared_lib_error_type_t  shared;
    } lib;
    const char* name;
};

struct json_litex_lib_error_ast_t
{
    struct json_error_pos_t pos;
    char msg[256];
};

enum json_litex_ruler_error_type_t
{
    json_litex_ruler_error_empty_object_not_allowed,
    json_litex_ruler_error_empty_array_not_allowed,
    json_litex_ruler_error_value_not_obj_not_arr_not_str,
};

struct json_litex_lib_error_meta_t
{
    enum json_litex_ruler_error_type_t type;
    struct json_error_pos_t            pos;
};

enum json_litex_lib_error_attr_type_t
{
    json_litex_lib_error_attr_invalid_string_empty,
    json_litex_lib_error_attr_invalid_object_empty_key,
    json_litex_lib_error_attr_invalid_object_dup_key,
    json_litex_lib_error_attr_invalid_array_inner_array,
    json_litex_lib_error_attr_invalid_array_multi_string,
    json_litex_lib_error_attr_invalid_array_multi_object,
};

struct json_litex_lib_error_attr_t
{
    enum json_litex_lib_error_attr_type_t type;
    struct json_error_pos_t               pos[2];
};

#define json_litex_lib_error_expr_t json_litex_expr_error_t

struct json_litex_lib_error_info_t
{
    enum json_litex_lib_error_t type;
    union {
        struct json_litex_lib_error_sys_t  sys;
        struct json_litex_lib_error_lib_t  lib;
        struct json_litex_lib_error_ast_t  ast;
        struct json_litex_lib_error_meta_t meta;
        struct json_litex_lib_error_attr_t attr;
        struct json_litex_lib_error_expr_t expr;
    };
    struct json_file_info_t file_info;
};

#define JSON_LITEX_RULER_RESULT(t, p, v)              \
    ({                                                \
        struct json_litex_ruler_result_t __r;         \
        __r.type = json_litex_ruler_ ## t ## _result; \
        __r.t = v;                                    \
        __r.pos = p;                                  \
        __r;                                          \
    })

#define JSON_LITEX_RULER_ERROR_POS_(p, e) \
        JSON_LITEX_RULER_RESULT(err, p, e)

#define JSON_LITEX_RULER_ERROR_POS(p, e)  \
        JSON_LITEX_RULER_ERROR_POS_(p,    \
            json_litex_ruler_error_ ## e)

#define JSON_LITEX_RULER_ERROR_NODE(n, t, e)     \
    ({                                           \
        const struct json_ast_node_t* __q =      \
            JSON_AST_TO_NODE_CONST(n, t);        \
        JSON_LITEX_RULER_ERROR_POS(__q->pos, e); \
    })

#define JSON_LITEX_RULER_ERROR(t, e) \
        JSON_LITEX_RULER_ERROR_NODE(node, t, e)

#define JSON_LITEX_RULER_VAL_ERROR(t) \
        JSON_LITEX_RULER_ERROR(t, value_not_obj_not_arr_not_str)

#define JSON_LITEX_RULER_VAL_(t, o)                \
    ({                                             \
        const struct json_ast_node_t* __q =        \
            JSON_AST_TO_NODE_CONST(node, t);       \
        JSON_LITEX_RULER_RESULT(val, __q->pos, o); \
    })

#define JSON_LITEX_RULER_VAL(t)                  \
        JSON_LITEX_RULER_VAL_(t,                 \
            json_litex_ruler_val_ ## t ## _type)

#define JSON_LITEX_RULER_CALL_RULE(t, n, ...)    \
    do {                                         \
        if (ruler->rules != NULL &&              \
            ruler->rules->n ## _rule != NULL) {  \
            const struct json_ast_node_t* __n =  \
                JSON_AST_TO_NODE_CONST(node, t); \
            ruler->rules->n ## _rule(            \
                ruler->rules_obj, &__n->pos,     \
                ## __VA_ARGS__);                 \
        }                                        \
    } while (0)

#define JSON_LITEX_RULER_CALL_RULE_POS(n, p, ...) \
    do {                                          \
        if (ruler->rules != NULL &&               \
            ruler->rules->n ## _rule != NULL) {   \
            ruler->rules->n ## _rule(             \
                ruler->rules_obj, &p, ##          \
                __VA_ARGS__);                     \
        }                                         \
    } while (0)

#define JSON_LITEX_RULER_VAL_STRING(v, d)  \
    ({                                     \
        JSON_LITEX_RULER_CALL_RULE(string, \
            str_val, v, d);                \
        JSON_LITEX_RULER_VAL(string);      \
    })

#define JSON_LITEX_RULER_VAL_OBJECT(v)     \
    ({                                     \
        JSON_LITEX_RULER_CALL_RULE(object, \
            obj_val, v);                   \
        JSON_LITEX_RULER_VAL(object);      \
    })

#define JSON_LITEX_RULER_VAL_ARRAY(v)     \
    ({                                    \
        JSON_LITEX_RULER_CALL_RULE(array, \
            arr_val, v);                  \
        JSON_LITEX_RULER_VAL(array);      \
    })

#define JSON_LITEX_RULER_CALL_OBJ_KEY_RULE(p, v, d) \
        JSON_LITEX_RULER_CALL_RULE_POS(obj_key, p, v, d)

#define JSON_LITEX_RULER_TYPEOF_IS_RESULT(r) \
    TYPEOF_IS(r, struct json_litex_ruler_result_t)

#define JSON_LITEX_RULER_RESULT_IS(r, n)              \
    (                                                 \
        STATIC(JSON_LITEX_RULER_TYPEOF_IS_RESULT(r)), \
        r.type == json_litex_ruler_ ## n ## _result   \
    )

#define JSON_LITEX_RULER_RESULT_IS_ERROR(r) \
        JSON_LITEX_RULER_RESULT_IS(r, err)

#define JSON_LITEX_RULER_RESULT_IS_VAL(r) \
        JSON_LITEX_RULER_RESULT_IS(r, val)

#define JSON_LITEX_RULER_VAL_TYPE(n) \
        json_litex_ruler_val_ ## n ## _type

#define JSON_LITEX_RULER_RESULT_VAL_IS(r, n)  \
    (                                         \
        JSON_LITEX_RULER_RESULT_IS_VAL(r) &&  \
        r.val == JSON_LITEX_RULER_VAL_TYPE(n) \
    )

enum json_litex_ruler_val_type_t
{
    json_litex_ruler_val_string_type,
    json_litex_ruler_val_object_type,
    json_litex_ruler_val_array_type,
};

enum json_litex_ruler_result_type_t
{
    json_litex_ruler_err_result,
    json_litex_ruler_val_result,
};

struct json_litex_ruler_result_t
{
    enum json_litex_ruler_result_type_t    type;
    union {
        enum json_litex_ruler_error_type_t err;
        enum json_litex_ruler_val_type_t   val;
    };
    struct json_text_pos_t pos;
};

// stev: the input of 'json-litex' consists of either
// strings, objects or arrays; the objects may have
// any number of arguments; the argument names have
// no constrains, but the type of each argument must
// be, recursively, the type itself; the arrays are
// closed, having one or two elements; the elements
// must be either strings or objects; if arrays have
// two elements, these cannot be both strings neither
// can they be both objects.

static struct json_litex_ruler_result_t
    json_litex_ruler_visit(
        struct json_litex_ruler_t* ruler,
        const struct json_ast_node_t* node);

static struct json_litex_ruler_result_t
    json_litex_ruler_null(
        struct json_litex_ruler_t* ruler UNUSED,
        const struct json_ast_null_node_t* node)
{
    return JSON_LITEX_RULER_VAL_ERROR(null);
}

static struct json_litex_ruler_result_t
    json_litex_ruler_boolean(
        struct json_litex_ruler_t* ruler UNUSED,
        const struct json_ast_boolean_node_t* node)
{
    return JSON_LITEX_RULER_VAL_ERROR(boolean);
}

static struct json_litex_ruler_result_t
    json_litex_ruler_number(
        struct json_litex_ruler_t* ruler UNUSED,
        const struct json_ast_number_node_t* node)
{
    return JSON_LITEX_RULER_VAL_ERROR(number);
}

static struct json_litex_ruler_result_t
    json_litex_ruler_string(
        struct json_litex_ruler_t* ruler,
        const struct json_ast_string_node_t* node)
{
    return JSON_LITEX_RULER_VAL_STRING(
        node->buf.ptr, node->delim);
}

static struct json_litex_ruler_result_t
    json_litex_ruler_object(
        struct json_litex_ruler_t* ruler,
        const struct json_ast_object_node_t* node)
{
    const struct json_ast_object_arg_t *p, *e;

    if (node->size == 0)
        return JSON_LITEX_RULER_ERROR(
            object, empty_object_not_allowed);

    for (p = node->args,
         e = p + node->size;
         p < e;
         p ++) {
        struct json_litex_ruler_result_t r;
        const struct json_ast_node_t* n;

        r = json_litex_ruler_visit(ruler, p->val);

        if (JSON_LITEX_RULER_RESULT_IS_ERROR(r))
            return r;

        n = JSON_AST_TO_NODE(p->key, string);

        JSON_LITEX_RULER_CALL_OBJ_KEY_RULE(
            n->pos, p->key->buf.ptr, p->key->delim);
    }

    return JSON_LITEX_RULER_VAL_OBJECT(node->size);
}

static struct json_litex_ruler_result_t
    json_litex_ruler_array(
        struct json_litex_ruler_t* ruler,
        const struct json_ast_array_node_t* node)
{
    struct json_ast_node_t *const *p, *const *e;

    if (node->size == 0)
        return JSON_LITEX_RULER_ERROR(
            array, empty_array_not_allowed);

    for (p = node->args,
         e = p + node->size;
         p < e;
         p ++) {
        struct json_litex_ruler_result_t r;

        r = json_litex_ruler_visit(ruler, *p);

        if (JSON_LITEX_RULER_RESULT_IS_ERROR(r))
            return r;
    }

    return JSON_LITEX_RULER_VAL_ARRAY(node->size);
}

#define JSON_AST_VISITOR_TYPE        1
#define JSON_AST_VISITOR_NAME        json_litex_ruler
#define JSON_AST_VISITOR_RESULT_TYPE struct json_litex_ruler_result_t
#define JSON_AST_VISITOR_OBJECT_TYPE struct json_litex_ruler_t

#define JSON_AST_NEED_VISITOR
#include "json-ast-impl.h"
#undef  JSON_AST_NEED_VISITOR

#define JSON_LITEX_LIB_META_ERROR(e, p) \
    ({                                  \
        err->type = e;                  \
        err->pos = p;                   \
        false;                          \
    })

static bool json_litex_ruler_eval(
    struct json_litex_ruler_t* ruler,
    const struct json_ast_t* ast,
    struct json_litex_lib_error_meta_t* err)
{
    struct json_litex_ruler_result_t r;
    const struct json_ast_node_t* n;

    ASSERT(ast != NULL);

    n = json_ast_get_root(ast);
    r = json_litex_ruler_visit(ruler, n);

    if (JSON_LITEX_RULER_RESULT_IS_ERROR(r))
        return JSON_LITEX_LIB_META_ERROR(r.err, r.pos);

    return true;
}

#undef  CASE
#define CASE(n) case json_litex_ruler_error_ ## n

static void json_litex_ruler_print_error_desc(
    enum json_litex_ruler_error_type_t type, FILE* file)
{
    size_t k = 0;

PRAGMA_DIAG_PUSH_IGNORED_IMPLICIT_FALLTHROUGH

    switch (type) {

    CASE(empty_object_not_allowed):
        k ++;
    CASE(empty_array_not_allowed):
        fprintf(file, "empty %s are not allowed",
            k ? "objects" : "arrays");
        break;

    CASE(value_not_obj_not_arr_not_str):
        fputs("values must be either strings, objects or arrays",
            file);
        break;

    default:
        UNEXPECT_VAR("%d", type);
    }

PRAGMA_DIAG_POP_IGNORED_IMPLICIT_FALLTHROUGH
}

static struct json_error_pos_t
    json_litex_lib_error_info_get_pos(
        const struct json_litex_lib_error_info_t* info)
{
    switch (info->type) {
    case json_litex_lib_error_none:
    case json_litex_lib_error_sys:
    case json_litex_lib_error_lib:
        return (struct json_error_pos_t) {0, 0};
    case json_litex_lib_error_ast:
        return info->ast.pos;
    case json_litex_lib_error_meta:
        return info->meta.pos;
    case json_litex_lib_error_attr:
        return info->attr.pos[0];
    case json_litex_lib_error_expr:
        return info->expr.pos;
    default:
        UNEXPECT_VAR("%d", info->type);
    }
}

static const struct json_file_info_t*
    json_litex_lib_error_info_get_file(
        const struct json_litex_lib_error_info_t* info)
{
    switch (info->type) {
    case json_litex_lib_error_none:
        return NULL;
    case json_litex_lib_error_sys:
    case json_litex_lib_error_lib:
    case json_litex_lib_error_ast:
    case json_litex_lib_error_meta:
    case json_litex_lib_error_attr:
    case json_litex_lib_error_expr:
        return &info->file_info;
    default:
        UNEXPECT_VAR("%d", info->type);
    }
}

static const char* json_litex_lib_error_sys_context_get_desc(
    enum json_litex_lib_error_sys_context_t context)
{
    switch (context) {
    case json_litex_lib_error_file_open_context:
        return "file open";
    case json_litex_lib_error_file_stat_context:
        return "file stat";
    case json_litex_lib_error_file_read_context:
        return "file read";
    case json_litex_lib_error_file_close_context:
        return "file close";
    default:
        UNEXPECT_VAR("%d", context);
    }
}

#undef  CASE
#define CASE(n) case json_litex_lib_generic_lib_error_ ## n

static void json_litex_lib_generic_lib_print_error_desc(
    enum json_litex_lib_generic_lib_error_type_t type,
    const char* path_name, FILE* file)
{
    switch (type) {
    CASE(path_name_not_available):
        fprintf(file, "path name '%s' not available", path_name);
        break;
    CASE(path_name_not_found):
        fprintf(file, "path name '%s' not found", path_name);
        break;
    default:
        UNEXPECT_VAR("%d", type);
    }
}

#undef  CASE
#define CASE(n) case json_litex_lib_shared_lib_error_ ## n

static const char* json_litex_lib_shared_lib_error_type_get_desc(
    enum json_litex_lib_shared_lib_error_type_t type)
{
    switch (type) {
    CASE(invalid_name):
        return "invalid library base name";
    CASE(load_lib_failed):
        return "failed loading library";
    CASE(get_version_sym_not_found):
        return "'get_version' symbol not found";
    CASE(get_def_sym_not_found):
        return "'get_def' symbol not found";
    CASE(wrong_lib_version):
        return "wrong library version";
    CASE(def_is_null):
        return "def is NULL";
    CASE(def_not_valid):
        return "def is not valid";
    default:
        UNEXPECT_VAR("%d", type);
    }
}

#define JSON_LITEX_TYPEOF_IS_(q, p, n) \
    TYPEOF_IS(p, q struct json_litex_ ## n ## _node_t*)

#define JSON_LITEX_TO_NODE_(q, p, n)            \
    ({                                          \
        q struct json_litex_node_t* __r;        \
        STATIC(JSON_LITEX_TYPEOF_IS_(q, p, n)); \
        __r = (q struct json_litex_node_t*)     \
            ((char*)(p) - offsetof(             \
                struct json_litex_node_t,       \
                node.n));                       \
        ASSERT(JSON_LITEX_NODE_IS_(q, __r, n)); \
        __r;                                    \
    })

#define JSON_LITEX_TYPEOF_IS(p, n) \
        JSON_LITEX_TYPEOF_IS_(, p, n)
#define JSON_LITEX_TYPEOF_IS_CONST(p, n) \
        JSON_LITEX_TYPEOF_IS_(const, p, n)

#define JSON_LITEX_TO_NODE(p, n) \
        JSON_LITEX_TO_NODE_(, p, n)
#define JSON_LITEX_TO_NODE_CONST(p, n) \
        JSON_LITEX_TO_NODE_(const, p, n)

#define JSON_LITEX_TYPEOF_IS_NODE_(q, p) \
    TYPEOF_IS(p, q struct json_litex_node_t*)

#define JSON_LITEX_TYPEOF_IS_NODE(p) \
        JSON_LITEX_TYPEOF_IS_NODE_(, p)

#define JSON_LITEX_TYPEOF_IS_NODE_CONST(p) \
        JSON_LITEX_TYPEOF_IS_NODE_(const, p)

#define JSON_LITEX_NODE_CONST_CAST(p) \
    CONST_CAST(p, struct json_litex_node_t)

#define JSON_LITEX_NODE_IS_(q, p, n)                \
    (                                               \
        STATIC(JSON_LITEX_TYPEOF_IS_NODE_(q, p)),   \
        (p)->type == json_litex_ ## n ## _node_type \
    )
#define JSON_LITEX_NODE_IS(p, n) \
        JSON_LITEX_NODE_IS_(, p, n)
#define JSON_LITEX_NODE_IS_CONST(p, n) \
        JSON_LITEX_NODE_IS_(const, p, n)

#define JSON_LITEX_NODE_AS_(q, p, n)          \
    ({                                        \
        ASSERT(JSON_LITEX_NODE_IS_(q, p, n)); \
        &((p)->node.n);                       \
    })
#define JSON_LITEX_NODE_AS(p, n) \
        JSON_LITEX_NODE_AS_(, p, n)
#define JSON_LITEX_NODE_AS_CONST(p, n) \
        JSON_LITEX_NODE_AS_(const, p, n)

#define JSON_LITEX_NODE_AS_IF_(q, p, n) \
    (                                   \
        JSON_LITEX_NODE_IS_(q, p, n)    \
        ? &((p)->node.n) : NULL         \
    )
#define JSON_LITEX_NODE_AS_IF(p, n) \
        JSON_LITEX_NODE_AS_IF_(, p, n)
#define JSON_LITEX_NODE_AS_IF_CONST(p, n) \
        JSON_LITEX_NODE_AS_IF_(const, p, n)

enum json_litex_print_repr_opts_t
{
    json_litex_print_repr_opts_non_print = SZ(1) << 0,
    json_litex_print_repr_opts_quotes    = SZ(1) << 1,

    json_litex_print_repr_opts_all =
        json_litex_print_repr_opts_quotes |
        json_litex_print_repr_opts_non_print,
    json_litex_print_repr_opts_none = 0,
};

#define JSON_LITEX_PRINT_REPR_OPTS_(n) \
        json_litex_print_repr_opts_ ## n
#define JSON_LITEX_PRINT_REPR_OPTS(...) \
    (VA_ARGS_REPEAT(|, JSON_LITEX_PRINT_REPR_OPTS_, \
        __VA_ARGS__))

static void json_litex_print_repr(
    const uchar_t* str,
    enum json_litex_print_repr_opts_t opts,
    FILE* file)
{
    STATIC(
         json_litex_print_repr_opts_non_print + 0 ==
            pretty_print_repr_quote_non_print &&

         json_litex_print_repr_opts_quotes + 0 ==
            pretty_print_repr_print_quotes);

    pretty_print_repr(file, str, strulen(str), opts);
}

union json_litex_node_pack_t
{
    const struct json_litex_string_node_t* string;
    const struct json_litex_object_node_t* object;
    const struct json_litex_array_node_t*  array;
};

static void json_litex_string_node_print(
    const struct json_litex_string_node_t* string,
    FILE* file)
{
    if (string->delim == NULL)
        json_litex_print_repr(string->val,
            JSON_LITEX_PRINT_REPR_OPTS(quotes), file);
    else
        fprintf(file, "r\"%s(%s)%s\"",
            string->delim, string->val,
            string->delim);
}

static void json_litex_node_print(
    const struct json_litex_node_t* node, FILE* file)
{
    union json_litex_node_pack_t n;

    if ((n.string = JSON_LITEX_NODE_AS_IF_CONST(
            node, string)))
        json_litex_string_node_print(n.string, file);
    else
    if ((n.object = JSON_LITEX_NODE_AS_IF_CONST(
            node, object))) {
        const struct json_litex_object_node_arg_t *p, *e;

        fputc('{', file);
        for (p = n.object->args,
             e = p + n.object->size;
             p < e;
             p ++) {
            json_litex_string_node_print(&p->key, file);
            fputc(':', file);
            json_litex_node_print(p->val, file);
            if (p < e - 1)
                fputc(',', file);
        }
        fputc('}', file);
    }
    else
    if ((n.array = JSON_LITEX_NODE_AS_IF_CONST(
            node, array))) {
        const struct json_litex_node_t **p, **e;

        fputc('[', file);
        for (p = n.array->args,
             e = p + n.array->size;
             p < e;
             p ++) {
            json_litex_node_print(*p, file);
            if (p < e - 1)
                fputc(',', file);
        }
        fputc(']', file);
    }
    else
        UNEXPECT_VAR("%d", node->type);
}

static void json_litex_def_print(
    const struct json_litex_def_t* def,
    FILE* file)
{
    json_litex_node_print(def->node, file);
}

enum json_litex_gen_def_opts_t
{
    json_litex_gen_def_opts_strcmp   = 1 << 0,
    json_litex_gen_def_opts_expanded = 1 << 1,

    json_litex_gen_def_opts_all =
    json_litex_gen_def_opts_strcmp |
    json_litex_gen_def_opts_expanded
};

#define JSON_LITEX_GEN_DEF_OPTS_(n) \
        json_litex_gen_def_opts_ ## n
#define JSON_LITEX_GEN_DEF_OPTS(...) \
    (VA_ARGS_REPEAT(|, JSON_LITEX_GEN_DEF_OPTS_, __VA_ARGS__))

#define JSON_LITEX_GEN_DEF_OPTS_IS__(n) \
    (space->opts & JSON_LITEX_GEN_DEF_OPTS_(n))
#define JSON_LITEX_GEN_DEF_OPTS_IS_(o, ...) \
    (VA_ARGS_REPEAT(o, JSON_LITEX_GEN_DEF_OPTS_IS__, __VA_ARGS__))
#define JSON_LITEX_GEN_DEF_OPTS_IS(...) \
        JSON_LITEX_GEN_DEF_OPTS_IS_(&&, __VA_ARGS__)

struct json_litex_ptr_space_t;

#define TREAP_NAME             json_litex_ptr
#define TREAP_KEY_TYPE         const void*
#define TREAP_VAL_TYPE         size_t
#define TREAP_KEY_IS_EQ(x, y)  ((x) == (y))
#define TREAP_KEY_CMP(x, y) \
    (                       \
          (x) == (y)        \
        ? treap_key_cmp_eq  \
        : (x) < (y)         \
        ? treap_key_cmp_lt  \
        : treap_key_cmp_gt  \
    )
#define TREAP_KEY_ASSIGN(x, y) ((x) = (y))
#define TREAP_ALLOC_OBJ_TYPE struct json_litex_ptr_space_t

#include "treap-impl.h"

struct json_litex_ptr_space_t
{
    struct pool_alloc_t pool;
    struct json_litex_ptr_treap_t map;
    enum json_litex_gen_def_opts_t opts;
    bits_t has_prefix: 1;
    bits_t has_equal: 1;
    size_t index;
};

static struct json_litex_ptr_treap_node_t*
    json_litex_ptr_space_new_treap_node(
        struct json_litex_ptr_space_t* space)
{
    struct json_litex_ptr_treap_node_t* n;

    if (!(n = pool_alloc_allocate(&space->pool,
            sizeof(struct json_litex_ptr_treap_node_t),
            MEM_ALIGNOF(struct json_litex_ptr_treap_node_t))))
        fatal_error("ptr-space pool alloc failed");

    memset(n, 0, sizeof(*n));

    return n;
}

static void json_litex_ptr_space_init(
    struct json_litex_ptr_space_t* space,
    enum json_litex_gen_def_opts_t opts,
    size_t size)
{
    const size_t n = sizeof(
        struct json_litex_ptr_treap_node_t);

    memset(space, 0, sizeof(
        struct json_litex_ptr_space_t));

    ASSERT_SIZE_MUL_NO_OVERFLOW(size, n);
    pool_alloc_init(&space->pool, size * n);

    json_litex_ptr_treap_init(
        &space->map,
        json_litex_ptr_space_new_treap_node,
        space);

    space->opts = opts;
}

static void json_litex_ptr_space_done(
    struct json_litex_ptr_space_t* space)
{
    pool_alloc_done(&space->pool);
}

#define JSON_LITEX_PTR_SPACE_VALIDATE(p)                \
    ({                                                  \
        struct json_litex_ptr_treap_node_t* __r = NULL; \
        bool __b = json_litex_ptr_treap_lookup_key(     \
            &space->map, p, &__r);                      \
        if (!__b || !__r)                               \
            *result = (p);                              \
        __b && __r;                                     \
    })
#define JSON_LITEX_PTR_SPACE_VALIDATE_NON_NULL(p) \
    (                                             \
        (p) == NULL ||                            \
        JSON_LITEX_PTR_SPACE_VALIDATE(p)          \
    )

#define JSON_LITEX_PTR_SPACE_VALIDATE_INSERT_(p)        \
    do {                                                \
        struct json_litex_ptr_treap_node_t* __r = NULL; \
        bool __b = json_litex_ptr_treap_insert_key(     \
            &space->map, ({ ASSERT(p); (p); }), &__r);  \
        ASSERT(__r != NULL);                            \
        if (!__b) {                                     \
            *result = (p);                              \
            return false;                               \
        }                                               \
        __r->val = space->index ++;                     \
    } while (0)

#define JSON_LITEX_PTR_SPACE_INSERT \
    JSON_LITEX_PTR_SPACE_VALIDATE_INSERT_

static bool json_litex_rex_def_validate(
    const struct json_litex_expr_rex_def_t* def,
    struct json_litex_ptr_space_t* space,
    const void** result)
{
    union json_litex_expr_rex_codes_pack_t p;

    ASSERT(def != NULL);

    JSON_LITEX_PTR_SPACE_INSERT(def);

    if ((p.patterns =
            JSON_LITEX_EXPR_REX_CODES_AS_IF_CONST(
                &def->codes, patterns))) {
        if (p.patterns == NULL) {
            *result = &p.patterns;
            return false;
        }
    }
    else
    if ((p.streams =
            JSON_LITEX_EXPR_REX_CODES_AS_IF_CONST(
                &def->codes, streams))) {
        if (p.streams == NULL) {
            *result = &p.streams;
            return false;
        }
    }
    else
        UNEXPECT_VAR("%d", def->codes.type);

    return true;
}

static bool json_litex_string_attr_validate(
    const struct json_litex_string_attr_t*,
    struct json_litex_ptr_space_t*,
    const void**);

static bool json_litex_object_attr_validate(
    const struct json_litex_object_attr_t*,
    struct json_litex_ptr_space_t*,
    const void**);

static bool json_litex_array_attr_validate(
    const struct json_litex_array_attr_t*,
    struct json_litex_ptr_space_t*,
    const void**);

static bool json_litex_node_validate(
    const struct json_litex_node_t* node,
    struct json_litex_ptr_space_t* space,
    const void** result)
{
    union json_litex_node_pack_t n;

    ASSERT(node != NULL);

    JSON_LITEX_PTR_SPACE_INSERT(node);

    if ((n.string = JSON_LITEX_NODE_AS_IF_CONST(
            node, string))) {
        if (node->attr.string == NULL) {
            *result = &node->attr.string;
            return false;
        }
        if (!json_litex_string_attr_validate(
                node->attr.string, space, result))
            return false;
    }
    else
    if ((n.object = JSON_LITEX_NODE_AS_IF_CONST(
            node, object))) {
        const struct json_litex_object_node_arg_t *p, *e;

        if (n.object->args == NULL) {
            *result = &n.object->args;
            return false;
        }
        JSON_LITEX_PTR_SPACE_INSERT(n.object->args);

        for (p = n.object->args,
             e = p + n.object->size;
             p < e;
             p ++) {
            if (p->val == NULL) {
                *result = &p->val;
                return false;
            }
            if (!json_litex_node_validate(
                        p->val, space, result))
                return false;
        }

        if (node->attr.object == NULL) {
            *result = &node->attr.object;
            return false;
        }
        if (!json_litex_object_attr_validate(
                node->attr.object, space, result))
            return false;

        if (!JSON_LITEX_PTR_SPACE_VALIDATE_NON_NULL(
                node->parent))
            return false;
    }
    else
    if ((n.array = JSON_LITEX_NODE_AS_IF_CONST(
            node, array))) {
        const struct json_litex_node_t **p, **e;

        if (n.array->args == NULL) {
            *result = &n.array->args;
            return false;
        }
        JSON_LITEX_PTR_SPACE_INSERT(n.array->args);

        for (p = n.array->args,
             e = p + n.array->size;
             p < e;
             p ++) {
            if (*p == NULL) {
                *result = n.array->args;
                return false;
            }
            if (!json_litex_node_validate(
                    *p, space, result))
                return false;
        }

        if (node->attr.array == NULL) {
            *result = &node->attr.array;
            return false;
        }
        if (!json_litex_array_attr_validate(
                node->attr.array, space, result))
            return false;

        if (!JSON_LITEX_PTR_SPACE_VALIDATE_NON_NULL(
                node->parent))
            return false;
    }
    else
        UNEXPECT_VAR("%d", node->type);

    *result = NULL;
    return true;
}

static bool json_litex_def_validate(
    const struct json_litex_def_t* def,
    struct json_litex_ptr_space_t* space,
    const void** result)
{
    if (def->rexes != NULL &&
        !json_litex_rex_def_validate(
            def->rexes, space, result))
        return false;

    if (def->node == NULL) {
        *result = &def->node;
        return false;
    }

    return json_litex_node_validate(
        def->node, space, result);
}

#define JSON_LITEX_PTR_SPACE_LOOKUP(p)                  \
    ({                                                  \
        struct json_litex_ptr_treap_node_t* __r = NULL; \
        bool __b = json_litex_ptr_treap_lookup_key(     \
            &space->map, p, &__r);                      \
        ASSERT(__b);                                    \
        ASSERT(__r);                                    \
        __r;                                            \
    })

#define JSON_LITEX_PTR_SPACE_GEN_PTR_INSERT_(p)         \
    ({                                                  \
        struct json_litex_ptr_treap_node_t* __r = NULL; \
        bool __b = json_litex_ptr_treap_insert_key(     \
            &space->map, ({ ASSERT(p); (p); }), &__r);  \
        ASSERT(__b);                                    \
        ASSERT(__r);                                    \
        __r->val = space->index ++;                     \
        __r;                                            \
    })

#undef  JSON_LITEX_PTR_SPACE_INSERT
#define JSON_LITEX_PTR_SPACE_INSERT \
        JSON_LITEX_PTR_SPACE_GEN_PTR_INSERT_

#define JSON_LITEX_UCHAR_TRIE_NULL_SYM() \
    ((uchar_t) 0)
#define JSON_LITEX_UCHAR_TRIE_SYM_IS_NULL(s) \
    (                                        \
        STATIC(TYPEOF_IS(s, uchar_t)),       \
        (s) == 0                             \
    )
#define JSON_LITEX_UCHAR_TRIE_SYM_CMP(x, y) \
    (                                       \
        STATIC(TYPEOF_IS(x, uchar_t)),      \
        STATIC(TYPEOF_IS(y, uchar_t)),      \
          (x) == (y)                        \
        ? trie_sym_cmp_eq                   \
        : (x) < (y)                         \
        ? trie_sym_cmp_lt                   \
        : trie_sym_cmp_gt                   \
    )
#define JSON_LITEX_UCHAR_TRIE_SYM_PRINT(s, f) \
    do {                                      \
        STATIC(TYPEOF_IS(s, uchar_t));        \
        STATIC(TYPEOF_IS(f, FILE*));          \
        pretty_print_string(f, &(s), 1,       \
            pretty_print_string_quotes);      \
    } while (0)
#define JSON_LITEX_UCHAR_TRIE_SYM_GEN_CODE(s, q, f) \
    do {                                            \
        STATIC(TYPEOF_IS(s, uchar_t));              \
        STATIC(TYPEOF_IS(f, FILE*));                \
        pretty_print_char(f, s,                     \
            q ? pretty_print_char_quotes : 0);      \
    } while (0)
#define JSON_LITEX_UCHAR_TRIE_ERR_GEN_CODE(f) \
    do {                                      \
        STATIC(TYPEOF_IS(f, FILE*));          \
        fputs("NULL", f);                     \
    } while (0)

#define JSON_LITEX_UCHAR_TRIE_KEY_TYPE     const uchar_t*
#define JSON_LITEX_UCHAR_TRIE_KEY_INC(k)   ((k) ++)
#define JSON_LITEX_UCHAR_TRIE_KEY_DEREF(k) (*(k))

struct json_litex_lib_t;

#define TRIE_NAME     json_litex
#define TRIE_SYM_TYPE uchar_t
#define TRIE_VAL_TYPE const struct json_litex_node_t*
#define TRIE_ALLOC_OBJ_TYPE struct json_litex_lib_t

#define TRIE_NULL_SYM    JSON_LITEX_UCHAR_TRIE_NULL_SYM
#define TRIE_SYM_IS_NULL JSON_LITEX_UCHAR_TRIE_SYM_IS_NULL
#define TRIE_SYM_CMP     JSON_LITEX_UCHAR_TRIE_SYM_CMP
#define TRIE_KEY_TYPE    JSON_LITEX_UCHAR_TRIE_KEY_TYPE
#define TRIE_KEY_INC     JSON_LITEX_UCHAR_TRIE_KEY_INC
#define TRIE_KEY_DEREF   JSON_LITEX_UCHAR_TRIE_KEY_DEREF

#define TRIE_SYM_PRINT   JSON_LITEX_UCHAR_TRIE_SYM_PRINT
#define TRIE_VAL_PRINT   json_litex_node_print

#define TRIE_NEED_PRINT_NODE
#define TRIE_NEED_INSERT_KEY
#define TRIE_NEED_LOOKUP_KEY_AT
#include "trie-impl.h"
#undef  TRIE_NEED_LOOKUP_KEY_AT
#undef  TRIE_NEED_INSERT_KEY
#undef  TRIE_NEED_PRINT_NODE

#define JSON_LITEX_TRIE_NODE_AS_VAL(p)     \
    TRIE_NODE_AS_VAL(                      \
        json_litex, p,                     \
        JSON_LITEX_UCHAR_TRIE_SYM_IS_NULL)

#define JSON_LITEX_TRIE_NODE_AS_VAL_REF(p)          \
    ({                                              \
        const struct json_litex_node_t* const* __r; \
        __r = TRIE_NODE_AS_VAL_REF(                 \
            json_litex, p,                          \
            JSON_LITEX_UCHAR_TRIE_SYM_IS_NULL);     \
        CONST_CAST(__r,                             \
            const struct json_litex_node_t*);       \
    })

static void json_litex_rex_def_gen_ptr(
    const struct json_litex_expr_rex_def_t* def,
    struct json_litex_ptr_space_t* space)
{
    ASSERT(def != NULL);
    ASSERT(def->size > 0);

    JSON_LITEX_PTR_SPACE_INSERT(&def->codes.code);
    if (def->elems != NULL)
        JSON_LITEX_PTR_SPACE_INSERT(def->elems);
    JSON_LITEX_PTR_SPACE_INSERT(def);
}

static void json_litex_string_attr_gen_ptr(
    const struct json_litex_string_attr_t*,
    struct json_litex_ptr_space_t*);

static void json_litex_object_attr_gen_ptr(
    const struct json_litex_object_attr_t*,
    struct json_litex_ptr_space_t*);

static void json_litex_array_attr_gen_ptr(
    const struct json_litex_array_attr_t*,
    struct json_litex_ptr_space_t*);

static void json_litex_node_gen_ptr(
    const struct json_litex_node_t* node,
    struct json_litex_ptr_space_t* space)
{
    union json_litex_node_pack_t n;

    if ((n.string = JSON_LITEX_NODE_AS_IF_CONST(
            node, string))) {
        json_litex_string_attr_gen_ptr(
            node->attr.string, space);
        JSON_LITEX_PTR_SPACE_INSERT(node);
    }
    else
    if ((n.object = JSON_LITEX_NODE_AS_IF_CONST(
            node, object))) {
        const struct json_litex_object_node_arg_t *p, *e;

        for (p = n.object->args,
             e = p + n.object->size;
             p < e;
             p ++) {
            json_litex_node_gen_ptr(p->val, space);
        }

        json_litex_object_attr_gen_ptr(
            node->attr.object, space);

        JSON_LITEX_PTR_SPACE_INSERT(n.object->args);
        JSON_LITEX_PTR_SPACE_INSERT(node);
    }
    else
    if ((n.array = JSON_LITEX_NODE_AS_IF_CONST(
            node, array))) {
        const struct json_litex_node_t **p, **e;

        for (p = n.array->args,
             e = p + n.array->size;
             p < e;
             p ++) {
            json_litex_node_gen_ptr(*p, space);
        }

        json_litex_array_attr_gen_ptr(
            node->attr.array, space);

        JSON_LITEX_PTR_SPACE_INSERT(n.array->args);
        JSON_LITEX_PTR_SPACE_INSERT(node);
    }
    else
        UNEXPECT_VAR("%d", node->type);
}

static void json_litex_expr_rex_codes_print(
    const struct json_litex_expr_rex_codes_t* codes,
    FILE* file)
{
    struct json_litex_expr_rex_bytes_t b;
    const uchar_t *p, *e;
    size_t k;

    json_litex_expr_rex_bytes_init(&b, codes);

    fprintf(file, "{\"size\":%zu,\"bytes\":[\"",
        b.size);

    for (k = 0,
         p = b.buf,
         e = p + b.size;
         p < e;
         p ++,
         k ++) {
        if (k && (k % 16) == 0)
            fputs("\",\"", file);
        fprintf(file, "%02x", *p);
    }
    ASSERT(k == b.size);

    fputs("\"]}", file);

    json_litex_expr_rex_bytes_done(&b);
}

#undef  CASE
#define CASE(n) \
    [json_litex_expr_rex_codes_ ## n] = #n

static void json_litex_rex_def_print(
    const struct json_litex_expr_rex_def_t* def,
    FILE* file)
{
    static const char* const codes[] = {
        CASE(patterns),
        CASE(streams),
    };
    const struct json_litex_expr_rex_t *p, *e;
    const char* n;

    if (!(n = ARRAY_NULL_ELEM(
                    codes, def->codes.type)))
        UNEXPECT_VAR("%d", def->codes.type);

    fprintf(file,
        "{\"size\":%zu,\"codes\":"
        "{\"type\":\"%s\",\"code\":",
        def->size, n);

    json_litex_expr_rex_codes_print(
        &def->codes, file);

    fputs("},\"elems\":", file);

    if (def->elems == NULL)
        fputs("null", file);
    else {
        fputc('[', file);

        for (p = def->elems,
             e = p + def->size;
             p < e;
             p ++) {
             json_litex_expr_rex_print(p, file);
             if (p < e - 1)
                fputc(',', file);
        }

        fputc(']', file);
    }

    fputc('}', file);
}

#undef  CASE
#define CASE(n) [json_litex_ ## n ## _node_type] = #n

static const char* json_litex_node_type_get_name(
    enum json_litex_node_type_t type)
{
    static const char* const types[] = {
        CASE(string),
        CASE(object),
        CASE(array)
    };
    const char* r = ARRAY_NULL_ELEM(
        types, type);

    if (r == NULL)
        UNEXPECT_VAR("%d", type);
    return r;
}

static void json_litex_string_attr_print(
    const struct json_litex_string_attr_t*,
    FILE*);

static void json_litex_object_attr_print(
    const struct json_litex_object_attr_t*,
    FILE*);

static void json_litex_array_attr_print(
    const struct json_litex_array_attr_t*,
    struct json_litex_ptr_space_t*,
    FILE*);

static void json_litex_string_print(
    const struct json_litex_string_node_t* string,
    FILE* file)
{
    fputs("{\"val\":", file);
    json_litex_print_repr(string->val,
        JSON_LITEX_PRINT_REPR_OPTS(quotes), file);

    fputs(",\"delim\":", file);
    if (string->delim != NULL)
        fprintf(file, "\"%s\"", string->delim);
    else
        fputs("null", file);

    fputc('}', file);
}

static void json_litex_node_print_attr(
    const struct json_litex_node_t* node,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    const struct json_litex_ptr_treap_node_t *t;
    const struct json_litex_object_node_t* q;
    union json_litex_node_pack_t n;

    ASSERT(node != NULL);

    t = JSON_LITEX_PTR_SPACE_LOOKUP(node);

    fprintf(file,
        "{\"id\":%zu,\"type\":\"%s\",\"node\":",
        t->val, json_litex_node_type_get_name(node->type));

    if ((n.string = JSON_LITEX_NODE_AS_IF_CONST(
            node, string))) {
        json_litex_string_print(
            n.string,
            file);
        fputs(",\"attr\":", file);
        json_litex_string_attr_print(
            node->attr.string,
            file);
    }
    else
    if ((n.object = JSON_LITEX_NODE_AS_IF_CONST(
            node, object))) {
        const struct json_litex_object_node_arg_t *p, *e;

        fprintf(file, "{\"size\":%zu,\"args\":[",
            n.object->size);
        for (p = n.object->args,
             e = p + n.object->size;
             p < e;
             p ++) {
            fputs("{\"key\":", file);
            json_litex_string_print(&p->key, file);
            fputs(",\"val\":", file);
            json_litex_node_print_attr(p->val, space, file);
            fputc('}', file);
            if (p < e - 1)
                fputc(',', file);
        }
        fputs("]},\"attr\":", file);

        json_litex_object_attr_print(
            node->attr.object,
            file);
    }
    else
    if ((n.array = JSON_LITEX_NODE_AS_IF_CONST(
            node, array))) {
        const struct json_litex_node_t **p, **e;

        fprintf(file, "{\"size\":%zu,\"args\":[",
            n.array->size);
        for (p = n.array->args,
             e = p + n.array->size;
             p < e;
             p ++) {
            json_litex_node_print_attr(*p, space, file);
            if (p < e - 1)
                fputc(',', file);
        }
        fputs("]},\"attr\":", file);

        json_litex_array_attr_print(
            node->attr.array,
            space, file);
    }
    else
        UNEXPECT_VAR("%d", node->type);

    // stev: node->path != NULL => node->parent != NULL
    ASSERT((node->path != NULL) <= (node->parent != NULL));

    fputs(",\"parent\":", file);
    if (node->parent == NULL)
        fputs("null", file);
    else {
        t = JSON_LITEX_PTR_SPACE_LOOKUP(node->parent);
        fprintf(file, "%zu", t->val);
    }

    fputs(",\"path\":", file);
    if (node->path == NULL)
        fputs("null", file);
    else {
        q = JSON_LITEX_NODE_AS_CONST(node->parent, object);
        fprintf(file, "%zu", PTR_DIFF(node->path, q->args));
    }

    fputc('}', file);
}

static void json_litex_def_print_attr(
    const struct json_litex_def_t* def,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    fputs("{\"rexes\":", file);
    if (def->rexes == NULL)
        fputs("null", file);
    else {
        json_litex_rex_def_gen_ptr(def->rexes, space);
        json_litex_rex_def_print(def->rexes, file);
    }

    fputs(",\"node\":", file);
    json_litex_node_gen_ptr(def->node, space);
    json_litex_node_print_attr(def->node, space, file);
    fputc('}', file);
}

static size_t json_litex_expr_rex_codes_gen_def(
    const struct json_litex_expr_rex_codes_t* codes,
    FILE* file)
{
    struct json_litex_expr_rex_bytes_t b;
    const uchar_t *p, *e;
    size_t k;

    json_litex_expr_rex_bytes_init(&b, codes);

    for (k = 0,
         p = b.buf,
         e = p + b.size;
         p < e;
         p ++,
         k ++) {
        if ((k % 8) == 0)
            fputs(k ? "\n   " : "   ", file);
        fprintf(file, " 0x%02x", *p);
        if (p < e - 1)
            fputc(',', file);
    }
    ASSERT(k == b.size);

    json_litex_expr_rex_bytes_done(&b);

    return k;
}

#define JSON_LITEX_EXPR_REX_OPT_GEN_DEF(n)         \
    do {                                           \
        if (JSON_LITEX_EXPR_REX_OPT_SET(           \
                rex->opts, n))                     \
            fprintf(file, "%s%s", k ++ ? "|" : "", \
                json_litex_expr_rex_opt_names[     \
                json_litex_expr_rex_opt_ ## n]);   \
    } while (0)

static void json_litex_expr_rex_gen_def(
    const struct json_litex_expr_rex_t* rex,
    FILE* file)
{
    fputs(
        "    {\n"
        "        .text = (const uchar_t*) ",
        file);

    json_litex_print_repr(rex->text,
        JSON_LITEX_PRINT_REPR_OPTS(quotes, non_print),
        file);

    if (rex->opts) {
        size_t k = 0;

        fputs(
            ",\n"
            "        .opts = ", file);

        JSON_LITEX_EXPR_REX_OPT_GEN_DEF(ignore_case);
        JSON_LITEX_EXPR_REX_OPT_GEN_DEF(dot_match_all);
        JSON_LITEX_EXPR_REX_OPT_GEN_DEF(no_utf8_check);
        JSON_LITEX_EXPR_REX_OPT_GEN_DEF(extended_pat);
    }

    fputs(
        "\n"
        "    }",
        file);
}

static void json_litex_rex_def_gen_def(
    const struct json_litex_expr_rex_def_t* def,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    const struct json_litex_ptr_treap_node_t *t, *u, *v;
    const struct json_litex_expr_rex_t *p, *e;
    size_t s;

    ASSERT(def != NULL);
    ASSERT(def->size > 0);

    t = JSON_LITEX_PTR_SPACE_LOOKUP(def);
    u = def->elems != NULL
      ? JSON_LITEX_PTR_SPACE_LOOKUP(def->elems)
      : NULL;
    v = JSON_LITEX_PTR_SPACE_LOOKUP(&def->codes.code);

    fprintf(file,
        "static const uchar_t __%zu[] = {\n",
        v->val);

    s = json_litex_expr_rex_codes_gen_def(
            &def->codes, file);

    fputs(
        "\n};\n\n",
        file);

    if (u != NULL) {
        fprintf(file,
            "#if 0\n"
            "static const struct json_litex_expr_rex_t __%zu[] = {\n",
            u->val);

        for (p = def->elems,
             e = p + def->size;
             p < e;
             p ++) {
            json_litex_expr_rex_gen_def(p, file);
            fputs(p < e - 1 ? ",\n" : "\n", file);
        }

        fputs(
            "};\n"
            "#endif\n\n",
            file);
    }

    fprintf(file,
        "static const struct json_litex_expr_rex_def_t __%zu = {\n"
        "    .codes = {\n"
        "        .type = json_litex_expr_rex_codes_streams,\n"
        "        .code.streams = {\n"
        "            .bytes = __%zu,\n"
        "            .size = %zu\n"
        "        }\n"
        "    },\n",
        t->val,
        v->val, s);

    if (u != NULL)
        fprintf(file,
            "#if 0\n"
            "    .elems = __%zu,\n"
            "#endif\n",
            u->val);

    fprintf(file,
        "    .size = %zu\n"
        "};\n\n",
        def->size);
}

struct json_litex_func_t;

#undef  TRIE_NAME
#define TRIE_NAME json_litex_func
#undef  TRIE_VAL_TYPE
#define TRIE_VAL_TYPE size_t
#undef  TRIE_ALLOC_OBJ_TYPE
#define TRIE_ALLOC_OBJ_TYPE struct json_litex_func_t

#define TRIE_SYM_GEN_CODE JSON_LITEX_UCHAR_TRIE_SYM_GEN_CODE
#define TRIE_ERR_GEN_CODE JSON_LITEX_UCHAR_TRIE_ERR_GEN_CODE
#define TRIE_VAL_GEN_CODE(p, f)      \
    do {                             \
        STATIC(TYPEOF_IS(f, FILE*)); \
        STATIC(TYPEOF_IS_SIZET(p));  \
        fprintf(f, "&__%zu", p);     \
    } while (0)

#define TRIE_NEED_GEN_CODE
#define TRIE_NEED_GEN_CODE_GET_ATTR
#define TRIE_NEED_INSERT_KEY
#include "trie-impl.h"
#undef  TRIE_NEED_GEN_CODE_GET_ATTR
#undef  TRIE_NEED_INSERT_KEY
#undef  TRIE_NEED_GEN_CODE

#define JSON_LITEX_FUNC_TRIE_NODE_AS_VAL(p) \
    TRIE_NODE_AS_VAL(                       \
        json_litex_func, p,                 \
        JSON_LITEX_UCHAR_TRIE_SYM_IS_NULL)

#define JSON_LITEX_FUNC_TRIE_NODE_AS_VAL_REF(p)   \
    ({                                            \
        const size_t* __r = TRIE_NODE_AS_VAL_REF( \
            json_litex_func, p,                   \
            JSON_LITEX_UCHAR_TRIE_SYM_IS_NULL);   \
        CONST_CAST(__r, size_t);                  \
    })

struct json_litex_func_t
{
    struct pool_alloc_t pool;
    struct json_litex_func_trie_t map;
};

static struct json_litex_func_trie_node_t*
    json_litex_func_new_trie_node(
        struct json_litex_func_t* func)
{
    struct json_litex_func_trie_node_t* n;

    if (!(n = pool_alloc_allocate(&func->pool,
            sizeof(struct json_litex_func_trie_node_t),
            MEM_ALIGNOF(struct json_litex_func_trie_node_t))))
        fatal_error("func pool alloc failed");

    memset(n, 0, sizeof(*n));

    return n;
}

static void json_litex_func_init(
    struct json_litex_func_t* func,
    struct json_litex_ptr_space_t* space,
    const struct json_litex_object_node_t* object)
{
    const struct json_litex_object_node_arg_t *p, *e;
    size_t n = 0;

    memset(func, 0, sizeof(struct json_litex_func_t));

    for (p = object->args,
         e = p + object->size;
         p < e;
         p ++) {
        size_t l = strulen(p->key.val);

        ASSERT_SIZE_INC_NO_OVERFLOW(l);
        SIZE_ADD_EQ(n, l + 1);
    }

    // stev: assume 'n' to be an upper bound for
    // the number of nodes in the trie 'map' -- which
    // has to contain all the arg names of 'object'
    pool_alloc_init(&func->pool, SIZE_MUL(
        n, sizeof(struct json_litex_func_trie_node_t)));

    json_litex_func_trie_init(
        &func->map, json_litex_func_new_trie_node, func);

    for (p = object->args,
         e = p + object->size;
         p < e;
         p ++) {
        const struct json_litex_func_trie_node_t* n = NULL;
        const struct json_litex_ptr_treap_node_t* t;

        if (!json_litex_func_trie_insert_key(
                &func->map, p->key.val, &n))
            ASSERT(false);

        t = JSON_LITEX_PTR_SPACE_LOOKUP(p->val);
        *JSON_LITEX_FUNC_TRIE_NODE_AS_VAL_REF(n) = t->val;
    }
}

static void json_litex_func_done(
    struct json_litex_func_t* func)
{
    pool_alloc_done(&func->pool);
}

static void json_litex_string_attr_gen_def(
    const struct json_litex_string_attr_t*,
    struct json_litex_ptr_space_t*,
    FILE*);

static void json_litex_object_attr_gen_def(
    const struct json_litex_object_attr_t*,
    struct json_litex_ptr_space_t*,
    struct json_litex_func_trie_t*,
    FILE*);

static void json_litex_array_attr_gen_def(
    const struct json_litex_array_attr_t*,
    struct json_litex_ptr_space_t*,
    FILE*);

static void json_litex_node_attr_gen_def(
    const struct json_litex_node_t* node,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    const struct json_litex_ptr_treap_node_t *t, *u;
    const struct json_litex_object_node_t* q;

    ASSERT(node != NULL);
    // stev: node->path != NULL => node->parent != NULL
    ASSERT((node->path != NULL) <= (node->parent != NULL));

    if (node->path != NULL) {
        q = JSON_LITEX_NODE_AS_CONST(node->parent, object);
        u = JSON_LITEX_PTR_SPACE_LOOKUP(q->args);

        fprintf(file,
            "    .path = __%zu + %zu,\n",
            u->val, PTR_DIFF(node->path, q->args));
    }
    if (node->parent != NULL) {
        t = JSON_LITEX_PTR_SPACE_LOOKUP(node->parent);

        fprintf(file,
            "    .parent = &__%zu\n",
            t->val);
    }
}

static void json_litex_node_gen_def(
    const struct json_litex_node_t* node,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    const struct json_litex_ptr_treap_node_t *t, *u, *v, *w;
    union json_litex_node_pack_t n;

    if ((n.string = JSON_LITEX_NODE_AS_IF_CONST(
            node, string))) {
        t = JSON_LITEX_PTR_SPACE_LOOKUP(node);

        json_litex_string_attr_gen_def(
            node->attr.string, space, file);

        u = JSON_LITEX_PTR_SPACE_LOOKUP(node->attr.string);

        fprintf(file,
            "static const struct json_litex_node_t __%zu = {\n"
            "    .type = json_litex_string_node_type,\n"
            "    .node.string = {\n"
            "        .val = (const uchar_t*) ",
            t->val);

        json_litex_print_repr(n.string->val,
            JSON_LITEX_PRINT_REPR_OPTS(quotes, non_print),
            file);

        if (n.string->delim != NULL)
            fprintf(file,
                ",\n"
                "        .delim = (const uchar_t*) \"%s\"",
                n.string->delim);

        fprintf(file,
            "\n"
            "    },\n"
            "    .attr.string = &__%zu",
            u->val);

        fputs(
            node->parent != NULL
            ? ",\n" : "\n",
            file);

        json_litex_node_attr_gen_def(
            node, space, file);

        fputs(
            "};\n\n",
            file);
    }
    else
    if ((n.object = JSON_LITEX_NODE_AS_IF_CONST(
            node, object))) {
        const struct json_litex_object_node_arg_t *p, *e;
        struct json_litex_func_t f;

        t = JSON_LITEX_PTR_SPACE_LOOKUP(node);
        u = JSON_LITEX_PTR_SPACE_LOOKUP(n.object->args);
        v = JSON_LITEX_PTR_SPACE_LOOKUP(node->attr.object);

        fprintf(file,
            "static const struct json_litex_object_node_arg_t "
                "__%zu[];\n\n"
            "static const struct json_litex_node_t "
                "__%zu;\n\n",
            u->val, t->val);

        for (p = n.object->args,
             e = p + n.object->size;
             p < e;
             p ++) {
            json_litex_node_gen_def(p->val, space, file);
        }

        json_litex_func_init(
            &f, space, n.object);
        json_litex_object_attr_gen_def(
            node->attr.object, space, &f.map, file);
        json_litex_func_done(&f);

        fprintf(file,
            "static const struct json_litex_object_node_arg_t "
                "__%zu[] = {\n",
            u->val);

        for (p = n.object->args,
             e = p + n.object->size;
             p < e;
             p ++) {
            w = JSON_LITEX_PTR_SPACE_LOOKUP(p->val);

            fputs(
                "    {\n"
                "        .key = {\n"
                "            .val = (const uchar_t*) ",
                file);

            json_litex_print_repr(p->key.val,
                JSON_LITEX_PRINT_REPR_OPTS(
                    quotes, non_print),
                file);

            if (p->key.delim != NULL)
                fprintf(file,
                    ",\n"
                    "        .delim = (const uchar_t*) \"%s\"",
                    p->key.delim);

            fprintf(file,
                "\n"
                "        },\n"
                "        .val = &__%zu\n"
                "    }%s\n",
                w->val, p < e - 1 ? "," : "");
        }
        fputs("};\n\n", file);

        fprintf(file,
            "static const struct json_litex_node_t __%zu = {\n"
            "    .type = json_litex_object_node_type,\n"
            "    .attr.object = &__%zu,\n"
            "    .node.object = {\n"
            "        .args = __%zu,\n"
            "        .size = %zu\n"
            "    }%s\n",
            t->val, v->val, u->val, n.object->size,
            node->parent != NULL ? "," : "");

        json_litex_node_attr_gen_def(
            node, space, file);

        fputs(
            "};\n\n",
            file);
    }
    else
    if ((n.array = JSON_LITEX_NODE_AS_IF_CONST(
            node, array))) {
        const struct json_litex_node_t **p, **e;

        t = JSON_LITEX_PTR_SPACE_LOOKUP(node);
        u = JSON_LITEX_PTR_SPACE_LOOKUP(n.array->args);
        v = JSON_LITEX_PTR_SPACE_LOOKUP(node->attr.array);

        fprintf(file,
            "static const struct json_litex_node_t "
                "__%zu;\n\n",
            t->val);

        for (p = n.array->args,
             e = p + n.array->size;
             p < e;
             p ++) {
            json_litex_node_gen_def(*p, space, file);
        }

        json_litex_array_attr_gen_def(
            node->attr.array, space, file);

        fprintf(file,
            "static const struct json_litex_node_t* __%zu[] = {\n",
            u->val);

        for (p = n.array->args,
             e = p + n.array->size;
             p < e;
             p ++) {
            w = JSON_LITEX_PTR_SPACE_LOOKUP(*p);
            fprintf(file,
                "    &__%zu,\n",
                w->val);
        }
        fputs(
            "    NULL\n"
            "};\n\n",
            file);

        fprintf(file,
            "static const struct json_litex_node_t __%zu = {\n"
            "    .type = json_litex_array_node_type,\n"
            "    .attr.array = &__%zu,\n"
            "    .node.array = {\n"
            "        .args = __%zu,\n"
            "        .size = %zu\n"
            "    }%s\n",
            t->val, v->val, u->val, n.array->size,
            node->parent != NULL ? "," : "");

        json_litex_node_attr_gen_def(
            node, space, file);

        fputs(
            "};\n\n",
            file);
    }
    else
        UNEXPECT_VAR("%d", node->type);
}

static void json_litex_def_gen_def(
    const struct json_litex_def_t* def,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    struct json_litex_ptr_treap_node_t* n;
    char b[64];

    fprintf(file,
        "//\n"
        "// JSON litex objects definitions.\n"
        "//\n"
        "// Product of `%s' library, version %s\n",
        STRINGIFY(PROGRAM),
        STRINGIFY(VERSION));
    if (json_get_current_time(b, sizeof(b)))
        fprintf(file, "// Generated on %s\n//\n\n", b);

    if (def->rexes != NULL) {
        ASSERT(def->rexes->size > 0);

        if (def->rexes->elems != NULL)
            fputs(
                "#if 0\n"
                "enum {\n"
                "  I = SZ(1) << json_litex_expr_rex_opt_ignore_case,\n"
                "  S = SZ(1) << json_litex_expr_rex_opt_dot_match_all,\n"
                "  U = SZ(1) << json_litex_expr_rex_opt_no_utf8_check,\n"
                "  X = SZ(1) << json_litex_expr_rex_opt_extended_pat,\n"
                "};\n"
                "#endif\n\n",
                file);

        json_litex_rex_def_gen_ptr(def->rexes, space);
        json_litex_rex_def_gen_def(def->rexes, space, file);
    }

    json_litex_node_gen_ptr(def->node, space);
    json_litex_node_gen_def(def->node, space, file);

    fputs(
        "static const struct json_litex_def_t MODULE_LITEX_DEF = {",
        file);

    if (def->rexes != NULL) {
        n = JSON_LITEX_PTR_SPACE_LOOKUP(def->rexes);
        fprintf(file,
            "\n"
            "    .rexes = &__%zu,",
            n->val);
    }

    n = JSON_LITEX_PTR_SPACE_LOOKUP(def->node);
    fprintf(file,
        "\n"
        "    .node = &__%zu\n"
        "};\n\n",
        n->val);
}

#undef  JSON_LITEX_PTR_SPACE_INSERT
#define JSON_LITEX_PTR_SPACE_INSERT \
        JSON_LITEX_PTR_SPACE_VALIDATE_INSERT_

static bool json_litex_string_attr_validate(
    const struct json_litex_string_attr_t* string,
    struct json_litex_ptr_space_t* space,
    const void** result)
{
    ASSERT(string != NULL);

    JSON_LITEX_PTR_SPACE_INSERT(string);

    if (string->nodes == NULL) {
        *result = &string->nodes;
        return false;
    }

    *result = NULL;
    return true;
}

#undef  JSON_LITEX_PTR_SPACE_INSERT
#define JSON_LITEX_PTR_SPACE_INSERT \
        JSON_LITEX_PTR_SPACE_GEN_PTR_INSERT_

static void json_litex_string_attr_gen_ptr(
    const struct json_litex_string_attr_t* string,
    struct json_litex_ptr_space_t* space)
{
    JSON_LITEX_PTR_SPACE_INSERT(string->nodes);
    JSON_LITEX_PTR_SPACE_INSERT(string);
}

static void json_litex_string_attr_print(
    const struct json_litex_string_attr_t* string,
    FILE* file)
{
    const struct json_litex_expr_node_t *p, *e;

    fprintf(file, "{\"size\":%zu,\"nodes\":[",
        string->size);

    for (p = string->nodes,
         e = p + string->size;
         p < e;
         p ++) {
         json_litex_expr_node_print(p, file);
         if (p < e - 1)
            fputc(',', file);
    }

    fputs("]}", file);
}

static void json_litex_string_attr_gen_def(
    const struct json_litex_string_attr_t* string,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    const struct json_litex_ptr_treap_node_t *t, *u;
    const struct json_litex_expr_node_t *p, *e;

    t = JSON_LITEX_PTR_SPACE_LOOKUP(string);
    u = JSON_LITEX_PTR_SPACE_LOOKUP(string->nodes);

    fprintf(file,
        "static const struct json_litex_expr_node_t __%zu[] = {\n",
        u->val);

    for (p = string->nodes,
         e = p + string->size;
         p < e;
         p ++) {
        json_litex_expr_node_gen_def(
            p, file);
        fputs(
            p < e - 1 ? ",\n" : "\n",
            file);
    }

    fprintf(file,
        "};\n\n"
        "static const struct json_litex_expr_def_t __%zu = {\n"
        "    .nodes = __%zu,\n"
        "    .size = %zu\n"
        "};\n\n",
        t->val, u->val, string->size);
}

#undef  JSON_LITEX_PTR_SPACE_INSERT
#define JSON_LITEX_PTR_SPACE_INSERT \
        JSON_LITEX_PTR_SPACE_VALIDATE_INSERT_

static bool json_litex_object_attr_validate(
    const struct json_litex_object_attr_t* object,
    struct json_litex_ptr_space_t* space,
    const void** result)
{
    ASSERT(object != NULL);

    JSON_LITEX_PTR_SPACE_INSERT(object);

    if (object->lookup == NULL) {
        *result = &object->lookup;
        return false;
    }

    *result = NULL;
    return true;
}

#undef  JSON_LITEX_PTR_SPACE_INSERT
#define JSON_LITEX_PTR_SPACE_INSERT \
        JSON_LITEX_PTR_SPACE_GEN_PTR_INSERT_

static void json_litex_object_attr_gen_ptr(
    const struct json_litex_object_attr_t* object,
    struct json_litex_ptr_space_t* space)
{
    JSON_LITEX_PTR_SPACE_INSERT(&object->root);
    JSON_LITEX_PTR_SPACE_INSERT(object);
}

static void json_litex_object_attr_print(
    const struct json_litex_object_attr_t* object,
    FILE* file)
{
    fprintf(file, "{\"lookup\":\"0x%zx\",\"root\":",
        PTR_TO_SIZE(object->lookup));
    json_litex_trie_print_node(
        object->root, file);
    fputc('}', file);
}

#define JSON_LITEX_PTR_SPACE_HAS_ATTR(a, n) \
    ({                                      \
        bool __a = (a) &                    \
            trie_gen_code_attrs_has_ ## n;  \
        bool __b = space->has_ ## n;        \
        space->has_ ## n |= __a;            \
        __a && !__b;                        \
    })

static void json_litex_object_attr_gen_def(
    const struct json_litex_object_attr_t* object,
    struct json_litex_ptr_space_t* space,
    struct json_litex_func_trie_t* func,
    FILE* file)
{
    const struct trie_gen_code_opts_t o = {
        .expanded = JSON_LITEX_GEN_DEF_OPTS_IS(expanded) != 0,
        .equal = JSON_LITEX_GEN_DEF_OPTS_IS(strcmp)
            ? "!strcmp" : "equal",
        .prefix = "prefix"
    };
    const struct json_litex_ptr_treap_node_t *t, *u;
    enum trie_gen_code_attrs_t a, b;

    t = JSON_LITEX_PTR_SPACE_LOOKUP(&object->root);
    u = JSON_LITEX_PTR_SPACE_LOOKUP(object);

    a = json_litex_func_trie_gen_code_get_attr(func, &o);

    if (JSON_LITEX_GEN_DEF_OPTS_IS(strcmp))
        a &= ~trie_gen_code_attrs_has_equal;

    if (JSON_LITEX_PTR_SPACE_HAS_ATTR(a, prefix))
        fputs(
            "static inline bool prefix(\n"
            "    const char* p, const char* q)\n"
            "{\n"
            "    while (*p && *p == *q)\n"
            "         ++ p, ++ q;\n"
            "    return *p == 0;\n"
            "}\n\n",
            file);

    if (JSON_LITEX_PTR_SPACE_HAS_ATTR(a, equal))
        fputs(
            "static inline bool equal(\n"
            "    const char* p, const char* q)\n"
            "{\n"
            "    while (*p && *p == *q)\n"
            "         ++ p, ++ q;\n"
            "    return *p == *q;\n"
            "}\n\n",
            file);

    fprintf(file,
        "static const struct json_litex_node_t* __%zu(\n"
        "    const struct json_litex_trie_node_t* n UNUSED,\n"
        "    const char* p)\n"
        "{\n",
        t->val);

    b = json_litex_func_trie_gen_code(func, &o, file);
    ASSERT(a == b);

    fprintf(file,
        "}\n\n"
        "static const struct json_litex_object_attr_t __%zu = {\n"
        "    .lookup = (json_litex_object_attr_lookup_func_t) __%zu\n"
        "};\n\n",
        u->val, t->val);
}

#undef  JSON_LITEX_PTR_SPACE_INSERT
#define JSON_LITEX_PTR_SPACE_INSERT \
        JSON_LITEX_PTR_SPACE_VALIDATE_INSERT_

static bool json_litex_array_attr_validate(
    const struct json_litex_array_attr_t* array,
    struct json_litex_ptr_space_t* space,
    const void** result)
{
    ASSERT(array != NULL);

    JSON_LITEX_PTR_SPACE_INSERT(array);

    if (!JSON_LITEX_PTR_SPACE_VALIDATE_NON_NULL(
            array->string))
        return false;

    if (!JSON_LITEX_PTR_SPACE_VALIDATE_NON_NULL(
            array->object))
        return false;

    *result = NULL;
    return true;
}

#undef  JSON_LITEX_PTR_SPACE_INSERT
#define JSON_LITEX_PTR_SPACE_INSERT \
        JSON_LITEX_PTR_SPACE_GEN_PTR_INSERT_

static void json_litex_array_attr_gen_ptr(
    const struct json_litex_array_attr_t* array,
    struct json_litex_ptr_space_t* space)
{
    JSON_LITEX_PTR_SPACE_INSERT(array);
}

#define JSON_LITEX_ARRAY_ATTR_PRINT(n)                      \
    do {                                                    \
        fputs("\"" #n "\":", file);                         \
        if (array->n == NULL)                               \
            fputs("null", file);                            \
        else {                                              \
            const struct json_litex_ptr_treap_node_t* __t = \
                JSON_LITEX_PTR_SPACE_LOOKUP(array->n);      \
            fprintf(file, "%zu", __t->val);                 \
        }                                                   \
        if (-- c) fputc(',', file);                         \
    } while (0)

static void json_litex_array_attr_print(
    const struct json_litex_array_attr_t* array,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    size_t c = 2;

    fputc('{', file);
    JSON_LITEX_ARRAY_ATTR_PRINT(string);
    JSON_LITEX_ARRAY_ATTR_PRINT(object);
    fputc('}', file);
}

#define JSON_LITEX_ARRAY_ATTR_GEN_DEF(n)                    \
    do {                                                    \
        if (array->n != NULL) {                             \
            const struct json_litex_ptr_treap_node_t* __t = \
                JSON_LITEX_PTR_SPACE_LOOKUP(array->n);      \
            fprintf(file, "    ." #n " = &__%zu%s\n",       \
                __t->val, -- c ? "," : "");                 \
        }                                                   \
    } while (0)

static void json_litex_array_attr_gen_def(
    const struct json_litex_array_attr_t* array,
    struct json_litex_ptr_space_t* space,
    FILE* file)
{
    const struct json_litex_ptr_treap_node_t* t;
    size_t c;

    c = (array->string != NULL) +
        (array->object != NULL);

    t = JSON_LITEX_PTR_SPACE_LOOKUP(array);

    fprintf(file,
        "static const struct json_litex_array_attr_t __%zu = {\n",
        t->val);

    JSON_LITEX_ARRAY_ATTR_GEN_DEF(string);
    JSON_LITEX_ARRAY_ATTR_GEN_DEF(object);

    fputs(
        "};\n\n",
        file);
}

enum json_litex_lib_obj_type_t
{
    json_litex_lib_obj_arg_type,
    json_litex_lib_obj_node_type,
};

struct json_litex_lib_obj_t
{
    enum json_litex_lib_obj_type_t          type;
    union {
        struct json_litex_object_node_arg_t arg;
        const struct json_litex_node_t*     node;
    };
};

#undef  STACK_NAME
#define STACK_NAME      json_litex_lib
#undef  STACK_ELEM_TYPE
#define STACK_ELEM_TYPE struct json_litex_lib_obj_t

#define STACK_NEED_MAX_SIZE
#define STACK_NEED_CREATE_DESTROY
#include "stack-impl.h"
#undef  STACK_NEED_CREATE_DESTROY
#undef  STACK_NEED_MAX_SIZE

#define JSON_LITEX_LIB_STACK_OP(o, ...)          \
    ({                                           \
        struct json_litex_lib_text_impl_t* __i;  \
        __i = JSON_LITEX_LIB_IMPL_AS(text);      \
        STACK_ ## o(__i->stack, ## __VA_ARGS__); \
    })
#define JSON_LITEX_LIB_STACK_PUSH(t, n)               \
    do {                                              \
        struct json_litex_lib_obj_t __o;              \
        __o.type = json_litex_lib_obj_ ## t ## _type; \
        __o.t = n;                                    \
        JSON_LITEX_LIB_STACK_OP(PUSH, __o);           \
    } while (0)

#define JSON_LITEX_LIB_STACK_PUSH_NODE(n) \
        JSON_LITEX_LIB_STACK_PUSH(node, n)
#define JSON_LITEX_LIB_STACK_PUSH_ARG(a) \
        JSON_LITEX_LIB_STACK_PUSH(arg, a)

#define JSON_LITEX_LIB_STACK_SIZE() \
        JSON_LITEX_LIB_STACK_OP(SIZE)
#define JSON_LITEX_LIB_STACK_POP() \
        JSON_LITEX_LIB_STACK_OP(POP)
#define JSON_LITEX_LIB_STACK_POP_N(n) \
        JSON_LITEX_LIB_STACK_OP(POP_N, n)
#define JSON_LITEX_LIB_STACK_TOP_REF() \
        JSON_LITEX_LIB_STACK_OP(TOP_REF)

#define JSON_LITEX_LIB_TYPEOF_IS_OBJ(p) \
    TYPEOF_IS(p, struct json_litex_lib_obj_t*)

#define JSON_LITEX_LIB_OBJ_IS(p, n)                    \
    (                                                  \
        STATIC(JSON_LITEX_LIB_TYPEOF_IS_OBJ(p)),       \
        (p)->type == json_litex_lib_obj_ ## n ## _type \
    )

#define JSON_LITEX_LIB_OBJ_AS(o, n)          \
    ({                                       \
        ASSERT(JSON_LITEX_LIB_OBJ_IS(o, n)); \
        (o)->n;                              \
    })

#define JSON_LITEX_LIB_OBJ_AS_IF(o, n) \
    (                                  \
        JSON_LITEX_LIB_OBJ_IS(o, n)    \
        ? (o)->n : NULL                \
    )

#define JSON_LITEX_LIB_STACK_POP_AS(n)    \
    ({                                    \
        struct json_litex_lib_obj_t __o;  \
        __o = JSON_LITEX_LIB_STACK_POP(); \
        JSON_LITEX_LIB_OBJ_AS(&__o, n);   \
    })

#define JSON_LITEX_LIB_STACK_POP_AS_ARG() \
        JSON_LITEX_LIB_STACK_POP_AS(arg)
#define JSON_LITEX_LIB_STACK_POP_AS_NODE() \
        JSON_LITEX_LIB_STACK_POP_AS(node)

#define JSON_LITEX_LIB_OBJ_IS_ARG(o) \
        JSON_LITEX_LIB_OBJ_IS(o, arg)
#define JSON_LITEX_LIB_OBJ_IS_NODE(o) \
        JSON_LITEX_LIB_OBJ_IS(o, node)

#define JSON_LITEX_LIB_OBJ_AS_ARG(o) \
        JSON_LITEX_LIB_OBJ_AS(o, arg)
#define JSON_LITEX_LIB_OBJ_AS_NODE(o) \
        JSON_LITEX_LIB_OBJ_AS(o, node)

#define JSON_LITEX_LIB_OBJ_AS_IF_NODE(o) \
        JSON_LITEX_LIB_OBJ_AS_IF(o, node)

#undef  PRINT_DEBUG_COND
#define PRINT_DEBUG_COND lib->debug

struct json_litex_lib_text_impl_t
{
    bits_t                         inited_buf: 1;
    bits_t                         done_build: 1;
    struct file_buf_t              buf;
    struct pool_alloc_t            pool;
    struct json_litex_lib_stack_t* stack;
    struct json_litex_expr_t*      expr;
    struct json_ast_t*             ast;
    struct json_litex_def_t        def;
};

#define json_litex_lib_sobj_impl_t dyn_lib_t

enum json_litex_lib_impl_type_t
{
    json_litex_lib_text_impl_type,
    json_litex_lib_sobj_impl_type
};

typedef const struct json_litex_def_t*
    (*json_litex_lib_impl_build_func_t)(
        struct json_litex_lib_t*,
        void*);
typedef bool (*json_litex_lib_impl_validate_func_t)(
    struct json_litex_lib_t*,
    void*);

struct json_litex_lib_impl_t
{
    enum json_litex_lib_impl_type_t       type;
    union {
        struct json_litex_lib_text_impl_t text;
        struct json_litex_lib_sobj_impl_t sobj;
    };
    json_litex_lib_impl_build_func_t      build;
    json_litex_lib_impl_validate_func_t   validate;
};

union json_litex_lib_impl_pack_t
{
    struct json_litex_lib_text_impl_t* text;
    struct json_litex_lib_sobj_impl_t* sobj;
};

struct json_litex_lib_sizes_t
{
    struct json_litex_expr_sizes_t expr;
    struct json_ast_sizes_t ast;
    size_t pool_size;
    size_t text_max_size;
    size_t ptr_space_size;
};

#define JSON_LITEX_LIB_SIZES_VALIDATE_(o)           \
    do {                                            \
        JSON_LITEX_EXPR_SIZES_VALIDATE_(o expr.);   \
        JSON_AST_SIZES_VALIDATE_(o ast.);           \
        JSON_SIZE_VALIDATE(o pool_size);            \
        JSON_SIZE_VALIDATE(o text_max_size);        \
        JSON_SIZE_VALIDATE(o ptr_space_size);       \
    } while (0)
#define JSON_LITEX_LIB_SIZES_VALIDATE(p)            \
    do {                                            \
        STATIC(JSON_TYPEOF_IS_SIZES(p, litex_lib)); \
        JSON_LITEX_LIB_SIZES_VALIDATE_(p->);        \
    } while (0)

enum json_litex_gen_attr_opts_t
{
    json_litex_gen_attr_opts_flat_expr_impl = 1 << 0,

    json_litex_gen_attr_opts_all =
    json_litex_gen_attr_opts_flat_expr_impl
};

#define JSON_LITEX_GEN_ATTR_OPTS_(n) \
        json_litex_gen_attr_opts_ ## n
#define JSON_LITEX_GEN_ATTR_OPTS(...) \
    (VA_ARGS_REPEAT(|, JSON_LITEX_GEN_ATTR_OPTS_, __VA_ARGS__))

#define JSON_LITEX_GEN_ATTR_OPTS_IS__(n) \
    (lib->opts.gen_attr & JSON_LITEX_GEN_ATTR_OPTS_(n))
#define JSON_LITEX_GEN_ATTR_OPTS_IS_(o, ...) \
    (VA_ARGS_REPEAT(o, JSON_LITEX_GEN_ATTR_OPTS_IS__, __VA_ARGS__))
#define JSON_LITEX_GEN_ATTR_OPTS_IS(...) \
        JSON_LITEX_GEN_ATTR_OPTS_IS_(&&, __VA_ARGS__)

struct json_litex_lib_opts_t
{
    enum json_litex_expr_opts_t     expr;
    enum json_litex_gen_attr_opts_t gen_attr;
    enum json_litex_gen_def_opts_t  gen_def;
};

struct json_litex_lib_alloc_info_t
{
    size_t size;
    void* ptr;
};

struct json_litex_lib_t
{
#ifdef JSON_DEBUG
    bits_t                             debug:
                                       debug_bits;
#endif
    struct json_litex_lib_sizes_t      sizes;
    struct json_litex_lib_opts_t       opts;
    struct json_litex_lib_impl_t       impl;
    const struct json_litex_def_t*     def;
    struct json_litex_lib_error_info_t error;
    struct json_litex_lib_alloc_info_t alloc;
};

#define JSON_LITEX_LIB_ERROR_IS(n) \
    (                              \
        lib->error.type ==         \
        json_litex_lib_error_ ## n \
    )

#define JSON_LITEX_LIB_SYS_BUF_ERROR(b)                 \
    ({                                                  \
        ASSERT(b.error_info.type > 0);                  \
        lib->error.type = json_litex_lib_error_sys;     \
        lib->error.sys.context = b.error_info.type - 1; \
        lib->error.sys.error = b.error_info.sys_error;  \
        lib->error.file_info.name = lib_name;           \
        lib->error.file_info.buf = NULL;                \
        lib->error.file_info.size = 0;                  \
        false;                                          \
    })

#define JSON_LITEX_LIB_SYS_ERROR(c, e)              \
    ({                                              \
        lib->error.type = json_litex_lib_error_sys; \
        lib->error.sys.context = c;                 \
        lib->error.sys.error = e;                   \
        lib->error.file_info.name = lib_name;       \
        lib->error.file_info.buf = NULL;            \
        lib->error.file_info.size = 0;              \
        false;                                      \
    })

#define JSON_LITEX_LIB_LIB_ERROR(e, l)              \
    ({                                              \
        lib->error.type = json_litex_lib_error_lib; \
        lib->error.lib = e;                         \
        lib->error.file_info.name = l;              \
        lib->error.file_info.buf = NULL;            \
        lib->error.file_info.size = 0;              \
        false;                                      \
    })

#define JSON_LITEX_LIB_AST_ERROR()                    \
    ({                                                \
        ASSERT(json_ast_get_is_error(impl->ast));     \
        lib->error.type = json_litex_lib_error_ast;   \
        json_litex_lib_get_ast_error(&lib->error.ast, \
            impl->ast);                               \
        lib->error.file_info.name = lib_name;         \
        lib->error.file_info.buf = NULL;              \
        lib->error.file_info.size = 0;                \
        false;                                        \
    })

#define JSON_LITEX_LIB_IMPL_IS(n) \
    (lib->impl.type == json_litex_lib_ ## n ## _impl_type)
#define JSON_LITEX_LIB_IMPL_AS(n)          \
    ({                                     \
        ASSERT(JSON_LITEX_LIB_IMPL_IS(n)); \
        &(lib->impl.n);                    \
    })
#define JSON_LITEX_LIB_IMPL_AS_IF(n) \
    (                                \
        JSON_LITEX_LIB_IMPL_IS(n)    \
        ? &(lib->impl.n) : NULL      \
    )

static void json_litex_lib_text_impl_init(
    struct json_litex_lib_text_impl_t* impl,
    size_t pool_size)
{
    pool_alloc_init(&impl->pool, pool_size);
}

static void json_litex_lib_sobj_impl_init(
    struct json_litex_lib_sobj_impl_t* impl,
    const char* lib_name)
{
    impl->lib_name = lib_name;
}

static void json_litex_lib_init_base(
    struct json_litex_lib_t* lib,
    enum json_litex_lib_impl_type_t type,
    const struct json_litex_lib_sizes_t* sizes,
    const struct json_litex_lib_opts_t* opts)
{
    memset(lib, 0, sizeof(struct json_litex_lib_t));

#ifdef JSON_DEBUG
    lib->debug = SIZE_TRUNC_BITS(
        json_litex_debug_get_level(
            json_litex_debug_lib_class),
        debug_bits);
#endif

    if (type == json_litex_lib_text_impl_type ||
        type == json_litex_lib_sobj_impl_type)
        lib->impl.type = type;
    else
        UNEXPECT_VAR("%d", type);

    lib->sizes = *sizes;
    lib->opts = *opts;
}

static void json_litex_lib_get_ast_error(
    struct json_litex_lib_error_ast_t* error,
    struct json_ast_t* ast);

static const struct json_litex_def_t*
    json_litex_lib_text_build(
        struct json_litex_lib_t*,
        struct json_litex_lib_text_impl_t*);

static bool json_litex_lib_text_validate(
    struct json_litex_lib_t*,
    struct json_litex_lib_text_impl_t*);

static const struct json_litex_def_t*
    json_litex_lib_sobj_build(
        struct json_litex_lib_t*,
        struct json_litex_lib_sobj_impl_t*);

static bool json_litex_lib_sobj_validate(
    struct json_litex_lib_t*,
    struct json_litex_lib_sobj_impl_t*);

#define JSON_LITEX_LIB_IMPL_FUNC_INIT(f, n) \
    lib->impl.f = (json_litex_lib_impl_ ## f ## _func_t) \
        json_litex_lib_ ## n ## _ ## f

#define JSON_LITEX_LIB_INIT_IMPL(n, ...)            \
    do {                                            \
        JSON_LITEX_LIB_IMPL_FUNC_INIT(build, n);    \
        JSON_LITEX_LIB_IMPL_FUNC_INIT(validate, n); \
        json_litex_lib_ ## n ## _impl_init(         \
            JSON_LITEX_LIB_IMPL_AS(n),              \
            ## __VA_ARGS__);                        \
    } while (0)

static void json_litex_lib_init_from_source_text(
    struct json_litex_lib_t* lib,
    const struct json_litex_lib_sizes_t* sizes,
    const struct json_litex_lib_opts_t* opts)
{
    json_litex_lib_init_base(
        lib, json_litex_lib_text_impl_type, sizes, opts);

    JSON_LITEX_LIB_INIT_IMPL(text, lib->sizes.pool_size);
}

static void json_litex_lib_init_from_shared_obj(
    struct json_litex_lib_t* lib, const char* lib_name,
    const struct json_litex_lib_sizes_t* sizes,
    const struct json_litex_lib_opts_t* opts)
{
    json_litex_lib_init_base(
        lib, json_litex_lib_sobj_impl_type, sizes, opts);

    JSON_LITEX_LIB_INIT_IMPL(sobj, lib_name);
}

static void json_litex_lib_text_impl_done(
    struct json_litex_lib_text_impl_t* impl)
{
    if (impl->ast != NULL)
        json_ast_destroy(impl->ast);
    if (impl->stack != NULL)
        json_litex_lib_stack_destroy(impl->stack);
    if (impl->expr != NULL)
        json_litex_expr_destroy(impl->expr);
    if (impl->inited_buf)
        file_buf_done(&impl->buf);
    pool_alloc_done(&impl->pool);
}

static void json_litex_lib_sobj_impl_done(
    struct json_litex_lib_sobj_impl_t* impl)
{
    dyn_lib_done(impl);
}

static void json_litex_lib_done(struct json_litex_lib_t* lib)
{
    union json_litex_lib_impl_pack_t p;

    if ((p.text = JSON_LITEX_LIB_IMPL_AS_IF(text)))
        json_litex_lib_text_impl_done(p.text);
    else
    if ((p.sobj = JSON_LITEX_LIB_IMPL_AS_IF(sobj)))
        json_litex_lib_sobj_impl_done(p.sobj);
    else
        UNEXPECT_VAR("%d", lib->impl.type);
}

#define JSON_LITEX_LIB_IMPL_PTR()                       \
    ({                                                  \
        void* __r;                                      \
        if (!(__r = JSON_LITEX_LIB_IMPL_AS_IF(text)) && \
            !(__r = JSON_LITEX_LIB_IMPL_AS_IF(sobj)))   \
            UNEXPECT_VAR("%d", lib->impl.type);         \
        __r;                                            \
    })

static bool json_litex_lib_text_parse_text(
    struct json_litex_lib_t* lib,
    struct json_litex_lib_text_impl_t* impl,
    const uchar_t* buf, size_t len,
    const char* lib_name)
{
    ASSERT(impl->ast == NULL);

    impl->ast = json_ast_create(&lib->sizes.ast);

    json_ast_config_set_param(
        impl->ast,
        json_allow_raw_strings_config,
        true);
    json_ast_config_set_param(
        impl->ast,
        json_allow_literal_value_config,
        true);

    // stev: TODO: give some thought whether this
    // parameter should be controlled by the user
    // of this class or not
    json_ast_config_set_param(
        impl->ast,
        json_validate_utf8_config,
        true);

    // stev: having literal expressions comes at a
    // price: cannot allow unicode escape sequences
    // "\u...." in the input, since otherwise would
    // be impossible to compute accurately textual
    // positions inside expressions without special
    // extra help from the JSON string parser

    // stev: TODO: for what concerns the restriction
    // imposed above, should remark that it prevents
    // paths to use strings containing JSON control
    // chars -- the chars in range ['\x0'...'\x1f'],
    // as per RFC7159 --, with the exception of '\b',
    // '\t', '\n', '\f', and '\r'; for allowing such
    // characters in path defs, the condition above
    // must be inverted: require all non-ASCII chars
    // and all JSON control chars be represented by
    // "\u...." escapes! Fact is that this condition
    // facilitates the accurate computation of text
    // positions inside literal expressions -- but
    // with an extra amount of work.

    json_ast_config_set_param(
        impl->ast,
        json_disallow_unicode_esc_config,
        true);

    if (json_ast_parse(impl->ast, buf, len) !=
        json_parse_status_ok ||
        json_ast_parse_done(impl->ast) !=
        json_parse_status_ok)
        return JSON_LITEX_LIB_AST_ERROR();

    return true;
}

static bool json_litex_lib_text_load_file(
    struct json_litex_lib_t* lib,
    struct json_litex_lib_text_impl_t* impl,
    const char* lib_name)
{
    STATIC(
        json_litex_lib_error_file_open_context ==
              file_buf_error_file_open - 1 &&

        json_litex_lib_error_file_stat_context ==
              file_buf_error_file_stat - 1 &&

        json_litex_lib_error_file_read_context ==
              file_buf_error_file_read - 1 &&

        json_litex_lib_error_file_close_context ==
              file_buf_error_file_close - 1);

    ASSERT(!impl->inited_buf);

    file_buf_init(
        &impl->buf, lib_name, lib->sizes.text_max_size);
    impl->inited_buf = true;

    if (impl->buf.error_info.type != file_buf_error_none)
        return JSON_LITEX_LIB_SYS_BUF_ERROR(impl->buf);

    return true;
}

#define JSON_LITEX_LIB_POOL() \
    (JSON_LITEX_LIB_IMPL_AS(text)->pool)

static void* json_litex_lib_allocate(
    struct json_litex_lib_t* lib, size_t size, size_t align)
{
    void* n;

    if (!(n = pool_alloc_allocate(
            &JSON_LITEX_LIB_POOL(), size, align)))
        fatal_error("path-lib pool alloc failed");

    lib->alloc.size = size;
    lib->alloc.ptr = n;

    memset(n, 0, size);

    return n;
}

static bool json_litex_lib_deallocate(
    struct json_litex_lib_t* lib, void* ptr)
{
    // stev: deallocate the last 'ptr' only
    if (ptr != NULL && lib->alloc.ptr == ptr) {
        lib->alloc.ptr = NULL;

        return pool_alloc_deallocate(
            &JSON_LITEX_LIB_POOL(),
            lib->alloc.size);
    }
    return false;
}

#define JSON_LITEX_LIB_ALLOCATE_STRUCT(t) \
    (                                     \
        json_litex_lib_allocate(          \
            lib, sizeof(struct t),        \
            MEM_ALIGNOF(struct t))        \
    )

static struct json_litex_trie_node_t*
    json_litex_lib_new_trie_node(
        struct json_litex_lib_t* lib)
{
    return JSON_LITEX_LIB_ALLOCATE_STRUCT(
        json_litex_trie_node_t);
}

#define JSON_LITEX_LIB_ALLOCATE_ARRAY(t, n)           \
    ({                                                \
        STATIC(TYPEOF_IS_SIZET(n));                   \
        ASSERT_SIZE_MUL_NO_OVERFLOW((n), sizeof(t));  \
        json_litex_lib_allocate(lib, (n) * sizeof(t), \
            MEM_ALIGNOF(t[0]));                       \
    })

static const uchar_t* json_litex_lib_new_str(
    struct json_litex_lib_t* lib, const uchar_t* str)
{
    uchar_t* p;
    size_t n;

    n = strulen(str);
    ASSERT_SIZE_INC_NO_OVERFLOW(n);
    n ++;

    p = JSON_LITEX_LIB_ALLOCATE_ARRAY(uchar_t, n);
    memcpy(p, str, n);

    return p;
}

static struct json_litex_node_t*
    json_litex_lib_new_node(
        struct json_litex_lib_t* lib,
        const struct json_text_pos_t* pos)
{
    struct json_litex_node_t* n;

    n = JSON_LITEX_LIB_ALLOCATE_STRUCT(
        json_litex_node_t);
    n->pos = *pos;

    return n;
}

static struct json_litex_string_node_t
    json_litex_lib_make_string(
        struct json_litex_lib_t* lib,
        const uchar_t* val, const uchar_t* delim)
{
    struct json_litex_string_node_t n;

    n.delim =
          delim != NULL && *delim
        ? json_litex_lib_new_str(lib, delim)
        : delim != NULL
        ? (const uchar_t*) ""
        : NULL;
    n.val = json_litex_lib_new_str(lib, val);

    return n;
}

static struct json_litex_node_t*
    json_litex_lib_new_string_node(
        struct json_litex_lib_t* lib,
        const struct json_text_pos_t* pos,
        const uchar_t* val, const uchar_t* delim)
{
    struct json_litex_node_t* n;

    n = json_litex_lib_new_node(lib, pos);

    n->type = json_litex_string_node_type;
    n->node.string = json_litex_lib_make_string(
        lib, val, delim);

    return n;
}

static struct json_litex_node_t*
    json_litex_lib_new_object_node(
        struct json_litex_lib_t* lib,
        const struct json_text_pos_t* pos,
        size_t size)
{
    struct json_litex_object_node_arg_t* a;
    struct json_litex_node_t* n;

    a = JSON_LITEX_LIB_ALLOCATE_ARRAY(
            struct json_litex_object_node_arg_t,
            size);

    n = json_litex_lib_new_node(lib, pos);

    n->type = json_litex_object_node_type;
    n->node.object.size = size;
    n->node.object.args = a;
    // stev: 'n->node.object.args' to be setup immediately

    return n;
}

static struct json_litex_node_t*
    json_litex_lib_new_array_node(
        struct json_litex_lib_t* lib,
        const struct json_text_pos_t* pos,
        size_t size)
{
    const struct json_litex_node_t** a;
    struct json_litex_node_t* n;

    ASSERT_SIZE_INC_NO_OVERFLOW(size);
    a = JSON_LITEX_LIB_ALLOCATE_ARRAY(
            struct json_litex_node_t*, size + 1);
    a[size] = NULL;

    n = json_litex_lib_new_node(lib, pos);

    n->type = json_litex_array_node_type;
    n->node.array.size = size;
    n->node.array.args = a;
    // stev: 'n->node.array.args' to be setup immediately

    return n;
}

static struct json_litex_object_attr_t*
    json_litex_lib_new_object_attr(
        struct json_litex_lib_t* lib)
{
    return JSON_LITEX_LIB_ALLOCATE_STRUCT(
        json_litex_object_attr_t);
}

static struct json_litex_array_attr_t*
    json_litex_lib_new_array_attr(
        struct json_litex_lib_t* lib)
{
    return JSON_LITEX_LIB_ALLOCATE_STRUCT(
        json_litex_array_attr_t);
}

static void json_litex_lib_rules_str_val(
    struct json_litex_lib_t* lib,
    const struct json_text_pos_t* pos,
    const uchar_t* val, const uchar_t* delim)
{
    struct json_litex_node_t* n;

    n = json_litex_lib_new_string_node(
            lib, pos, val, delim);

    JSON_LITEX_LIB_STACK_PUSH_NODE(n);
}

static void json_litex_lib_rules_obj_key(
    struct json_litex_lib_t* lib,
    const struct json_text_pos_t* pos,
    const uchar_t* val, const uchar_t* delim)
{
    struct json_litex_object_node_arg_t a;

    a.val = JSON_LITEX_LIB_STACK_POP_AS_NODE();
    a.key = json_litex_lib_make_string(
                lib, val, delim);
    a.pos = *pos;

    JSON_LITEX_LIB_STACK_PUSH_ARG(a);
}

static void json_litex_lib_rules_obj_val(
    struct json_litex_lib_t* lib,
    const struct json_text_pos_t* pos,
    size_t size)
{
    struct json_litex_node_t* n;

    n = json_litex_lib_new_object_node(lib, pos, size);
    if (size > 0) {
        struct json_litex_lib_obj_t *b, *p;
        struct json_litex_object_node_t* o;
        struct json_litex_object_node_arg_t* q;

        o = JSON_LITEX_NODE_AS(n, object);

        ASSERT(o->size == size);

        ASSERT(JSON_LITEX_LIB_STACK_SIZE() >= size);
        for (b = JSON_LITEX_LIB_STACK_TOP_REF(),
             p = b - SIZE_AS_INT(size - 1),
             q = CONST_CAST(o->args,
                struct json_litex_object_node_arg_t);
             p <= b;
             p ++,
             q ++) {
             *q = JSON_LITEX_LIB_OBJ_AS_ARG(p);
        }
        JSON_LITEX_LIB_STACK_POP_N(size - 1);
    }

    JSON_LITEX_LIB_STACK_PUSH_NODE(n);
}

static void json_litex_lib_rules_arr_val(
    struct json_litex_lib_t* lib,
    const struct json_text_pos_t* pos,
    size_t size)
{
    struct json_litex_node_t* n;

    n = json_litex_lib_new_array_node(lib, pos, size);
    if (size > 0) {
        struct json_litex_lib_obj_t *b, *p;
        const struct json_litex_node_t** q;
        struct json_litex_array_node_t* a;

        a = JSON_LITEX_NODE_AS(n, array);

        ASSERT(a->size == size);

        ASSERT(JSON_LITEX_LIB_STACK_SIZE() >= size);
        for (b = JSON_LITEX_LIB_STACK_TOP_REF(),
             p = b - SIZE_AS_INT(size - 1),
             q = a->args;
             p <= b;
             p ++,
             q ++) {
             *q = JSON_LITEX_LIB_OBJ_AS_NODE(p);
        }
        JSON_LITEX_LIB_STACK_POP_N(size - 1);
    }

    JSON_LITEX_LIB_STACK_PUSH_NODE(n);
}

static const struct json_litex_ruler_rules_t
json_litex_lib_rules = {
    .str_val_rule = (json_litex_ruler_string_rule_t)
                     json_litex_lib_rules_str_val,
    .obj_key_rule = (json_litex_ruler_string_rule_t)
                     json_litex_lib_rules_obj_key,
    .obj_val_rule = (json_litex_ruler_size_rule_t)
                     json_litex_lib_rules_obj_val,
    .arr_val_rule = (json_litex_ruler_size_rule_t)
                     json_litex_lib_rules_arr_val,
};

static void json_litex_lib_get_ast_error(
    struct json_litex_lib_error_ast_t* error,
    struct json_ast_t* ast)
{
    enum { n = ARRAY_SIZE(error->msg) };
    FILE* f;

    STATIC(n > 0);
    error->pos = json_ast_get_error_pos(ast);

    if ((f = fmemopen(error->msg, n - 1, "w"))) {
        json_ast_print_error_desc(ast, f);
        fclose(f);

        error->msg[n - 1] = 0;
    }
    else {
        STATIC(n >= 4);
        strcpy(error->msg, "???");
    }
}

#undef  CASE
#define CASE(n) case json_litex_lib_error_attr_ ## n

static void json_litex_lib_attr_print_error_desc(
    const struct json_litex_lib_error_attr_t* attr,
    FILE* file)
{
    size_t k = 0;

PRAGMA_DIAG_PUSH_IGNORED_IMPLICIT_FALLTHROUGH

    switch (attr->type) {
    CASE(invalid_string_empty):
        fputs("invalid string: is empty", file);
        break;
    CASE(invalid_object_empty_key):
        fputs("invalid object: key is empty", file);
        break;
    CASE(invalid_object_dup_key):
        fprintf(file, "invalid object: duplicated key name "
            "(previous defined at %zu:%zu)",
            attr->pos[1].line,
            attr->pos[1].col);
        break;
    CASE(invalid_array_inner_array):
        fprintf(file, "invalid array: inner arrays not allowed "
            "(the outer array begins at %zu:%zu)",
            attr->pos[1].line,
            attr->pos[1].col);
        break;
    CASE(invalid_array_multi_string):
        k ++;
    CASE(invalid_array_multi_object):
        fprintf(file, "invalid array: multiple inner %s "
            "(previous defined at %zu:%zu)",
            k ? "strings" : "objects",
            attr->pos[1].line,
            attr->pos[1].col);
        break;
    default:
        UNEXPECT_VAR("%d", attr->type);
    }

PRAGMA_DIAG_POP_IGNORED_IMPLICIT_FALLTHROUGH
}

static void json_litex_lib_error_info_print_error_desc(
    const struct json_litex_lib_error_info_t* info,
    FILE* file)
{
    switch (info->type) {
    case json_litex_lib_error_none:
        fputc('-', file);
        break;
    case json_litex_lib_error_sys:
        fputs("system error: ", file);
        ASSERT(info->sys.error);
        fprintf(file, "%s: %s",
            json_litex_lib_error_sys_context_get_desc(
                info->sys.context),
            strerror(info->sys.error));
        break;
    case json_litex_lib_error_lib:
        fputs("path library: ", file);
        if (info->lib.type == json_litex_lib_error_generic_lib)
            json_litex_lib_generic_lib_print_error_desc(
                info->lib.lib.generic,
                info->lib.name,
                file);
        else
        if (info->lib.type == json_litex_lib_error_shared_lib)
            fputs(json_litex_lib_shared_lib_error_type_get_desc(
                info->lib.lib.shared), file);
        else
            UNEXPECT_VAR("%d", info->lib.type);
        break;
    case json_litex_lib_error_ast:
        // stev: not printing an "ast error:"
        // heading since the 'json_ast' class
        // is not producing own errors -- the
        // only errors it produces are due to
        // its inner 'json_obj' instance
        fputs(info->ast.msg, file);
        break;
    case json_litex_lib_error_meta:
        fputs("meta error: ", file);
        json_litex_ruler_print_error_desc(
            info->meta.type, file);
        break;
    case json_litex_lib_error_attr:
        fputs("attribute error: ", file);
        json_litex_lib_attr_print_error_desc(
            &info->attr, file);
        break;
    case json_litex_lib_error_expr:
        fputs("expression error: ", file);
        json_litex_expr_print_error_desc(
            &info->expr, file);
        break;
    default:
        UNEXPECT_VAR("%d", info->type);
    }
}

static bool json_litex_lib_get_is_error(
    struct json_litex_lib_t* lib)
{
    return !JSON_LITEX_LIB_ERROR_IS(none);
}

static struct json_error_pos_t
    json_litex_lib_get_error_pos(
        struct json_litex_lib_t* lib)
{
    return json_litex_lib_error_info_get_pos(
        &lib->error);
}

static const struct json_file_info_t*
    json_litex_lib_get_error_file(
        struct json_litex_lib_t* lib)
{
    return json_litex_lib_error_info_get_file(
        &lib->error);
}

static void json_litex_lib_print_error_desc(
    struct json_litex_lib_t* lib,
    FILE* file)
{
    json_litex_lib_error_info_print_error_desc(
        &lib->error, file);
}

#undef  JSON_LITEX_LIB_META_ERROR
#define JSON_LITEX_LIB_META_ERROR(m)         \
    ({                                       \
        lib->error.type =                    \
            json_litex_lib_error_meta;       \
        lib->error.meta = m;                 \
        lib->error.file_info.name = NULL;    \
        lib->error.file_info.buf = NULL;     \
        lib->error.file_info.size = 0;       \
        false;                               \
    })

#define JSON_LITEX_LIB_ATTR_ERROR2(n, p0, p1) \
    ({                                        \
        lib->error.type =                     \
            json_litex_lib_error_attr;        \
        lib->error.attr.type =                \
            json_litex_lib_error_attr_ ## n;  \
        lib->error.attr.pos[0] = p0;          \
        lib->error.attr.pos[1] = p1;          \
        lib->error.file_info.name = NULL;     \
        lib->error.file_info.buf = NULL;      \
        lib->error.file_info.size = 0;        \
        false;                                \
    })
#define JSON_LITEX_LIB_ATTR_ERROR(n, p) \
    JSON_LITEX_LIB_ATTR_ERROR2(n, p, \
        ((struct json_error_pos_t){0,0}))

#define JSON_LITEX_LIB_EXPR_ERROR()          \
    ({                                       \
        lib->error.type =                    \
            json_litex_lib_error_expr;       \
        lib->error.expr = impl->expr->error; \
        lib->error.file_info.name = NULL;    \
        lib->error.file_info.buf = NULL;     \
        lib->error.file_info.size = 0;       \
        false;                               \
    })

#define JSON_LITEX_LIB_NONE_ERROR()       \
    ({                                    \
        lib->error.type =                 \
            json_litex_lib_error_none;    \
        lib->error.file_info.name = NULL; \
        lib->error.file_info.buf = NULL;  \
        lib->error.file_info.size = 0;    \
        true;                             \
    })

static bool json_litex_lib_text_validate_base(
    struct json_litex_lib_t* lib,
    struct json_litex_lib_text_impl_t* impl,
    bool valid_only)
{
    struct json_litex_lib_error_meta_t e;
    struct json_litex_ruler_t t;
    bool r;

    if (!JSON_LITEX_LIB_ERROR_IS(none))
        return false;

    ASSERT(impl->ast != NULL);

    json_litex_ruler_init(&t,
        !valid_only ? &json_litex_lib_rules : NULL,
        !valid_only ? lib : NULL);
    r = json_litex_ruler_eval(&t, impl->ast, &e);
    json_litex_ruler_done(&t);

    json_ast_destroy(impl->ast);
    impl->ast = NULL;

    if (!r)
        return JSON_LITEX_LIB_META_ERROR(e);
    else
        return JSON_LITEX_LIB_NONE_ERROR();
}

static bool json_litex_lib_text_validate(
    struct json_litex_lib_t* lib UNUSED,
    struct json_litex_lib_text_impl_t* impl UNUSED)
{
    return true; // stev: nop
}

static bool json_litex_lib_validate(
    struct json_litex_lib_t* lib)
{
    return lib->impl.validate(
        lib, JSON_LITEX_LIB_IMPL_PTR());
}

static bool json_litex_lib_gen_string_attrs(
    struct json_litex_lib_t* lib,
    const struct json_litex_string_node_t* string,
    const struct json_litex_string_attr_t** result)
{
    struct json_litex_lib_text_impl_t* impl;
    const struct json_litex_node_t* p;
    const uchar_t* s;
    size_t l, o;

    impl = JSON_LITEX_LIB_IMPL_AS(text);

    p = JSON_LITEX_TO_NODE_CONST(string, string);
    s = string->val;
    l = strulen(s);

    // stev: raw strings begin with 'r"' DELIM '('
    o = string->delim != NULL
        ? strulen(string->delim) + 3 : 1;

    if (!json_litex_expr_compile(
            impl->expr, s, l, &p->pos, o, result))
        return JSON_LITEX_LIB_EXPR_ERROR();

    return true;
}

static const struct json_litex_node_t*
    json_litex_trie_lookup_name(
        const struct json_litex_trie_node_t* root,
        const uchar_t* name)
{
    const struct json_litex_trie_node_t* n = NULL;

    ASSERT(root != NULL);

    if (!json_litex_trie_lookup_key_at(root,  name, &n))
        return NULL;

    ASSERT(n != NULL);
    return JSON_LITEX_TRIE_NODE_AS_VAL(n);
}

static bool json_litex_lib_gen_object_attrs(
    struct json_litex_lib_t* lib,
    const struct json_litex_object_node_t* object,
    const struct json_litex_object_attr_t** result)
{
    const struct json_litex_object_node_arg_t *p, *e;
    struct json_litex_object_attr_t* r;
    const struct json_litex_node_t* n;
    struct json_litex_trie_t t;

    ASSERT(JSON_LITEX_LIB_IMPL_IS(text));

    r = json_litex_lib_new_object_attr(lib);
    n = JSON_LITEX_TO_NODE_CONST(object, object);

    json_litex_trie_init(
        &t, json_litex_lib_new_trie_node, lib);

    for (p = object->args,
         e = p + object->size;
         p < e;
         p ++) {
        const struct json_litex_trie_node_t *m = NULL;
        struct json_litex_node_t* q;

        if (*p->key.val == 0)
            return JSON_LITEX_LIB_ATTR_ERROR(
                invalid_object_empty_key,
                p->pos);

        q = CONST_CAST(p->val, struct json_litex_node_t);
        ASSERT(q->parent == NULL);
        ASSERT(q->path == NULL);
        q->parent = n;
        q->path = p;

        if (!json_litex_trie_insert_key(&t, p->key.val, &m)) {
            const struct json_litex_object_node_arg_t *q;
            const struct json_litex_node_t* v;

            for (v = JSON_LITEX_TRIE_NODE_AS_VAL(m),
                 q = object->args;
                 q < p;
                 q ++) {
                if (q->val == v)
                    break;
            }
            ASSERT(q < p);

            return JSON_LITEX_LIB_ATTR_ERROR2(
                invalid_object_dup_key,
                p->pos, q->pos);
        }
        *JSON_LITEX_TRIE_NODE_AS_VAL_REF(m) = p->val;
    }

    r->lookup = json_litex_trie_lookup_name;
    r->root = t.root;

    *result = r;
    return true;
}

static bool json_litex_lib_gen_array_attrs(
    struct json_litex_lib_t* lib,
    const struct json_litex_array_node_t* array,
    const struct json_litex_array_attr_t** result)
{
    const struct json_litex_node_t **p, **e, *n;
    struct json_litex_array_attr_t* r;

    ASSERT(JSON_LITEX_LIB_IMPL_IS(text));

    r = json_litex_lib_new_array_attr(lib);
    n = JSON_LITEX_TO_NODE_CONST(array, array);

    for (p = array->args,
         e = p + array->size;
         p < e;
         p ++) {
        struct json_litex_node_t* q;

        if (JSON_LITEX_NODE_IS_CONST(*p, string)) {
            if (r->string != NULL)
                return JSON_LITEX_LIB_ATTR_ERROR2(
                    invalid_array_multi_string,
                    (*p)->pos, r->string->pos);
            else
                r->string = *p;
        }
        else
        if (JSON_LITEX_NODE_IS_CONST(*p, object)) {
            if (r->object != NULL)
                return JSON_LITEX_LIB_ATTR_ERROR2(
                    invalid_array_multi_object,
                    (*p)->pos, r->object->pos);
            else
                r->object = *p;
        }
        else
        if (JSON_LITEX_NODE_IS_CONST(*p, array)) {
            const struct json_litex_node_t* q =
                JSON_LITEX_TO_NODE_CONST(
                    array, array);
            return JSON_LITEX_LIB_ATTR_ERROR2(
                invalid_array_inner_array,
                (*p)->pos, q->pos);
        }
        else
            UNEXPECT_VAR("%d", (*p)->type);

        q = CONST_CAST(*p, struct json_litex_node_t);
        ASSERT(q->parent == NULL);
        ASSERT(q->path == NULL);
        q->parent = n;
    }

    *result = r;
    return true;
}

static bool json_litex_lib_gen_node_attrs(
    struct json_litex_lib_t* lib,
    const struct json_litex_node_t* node)
{
    union json_litex_node_pack_t n;

    if ((n.string = JSON_LITEX_NODE_AS_IF_CONST(
            node, string))) {
        const struct json_litex_string_attr_t** a;

        if (*n.string->val == 0)
            return JSON_LITEX_LIB_ATTR_ERROR(
                invalid_string_empty,
                node->pos);

        a = &JSON_LITEX_NODE_CONST_CAST(node)->attr.string;
        if (!json_litex_lib_gen_string_attrs(lib, n.string, a))
            return false;
    }
    else
    if ((n.object = JSON_LITEX_NODE_AS_IF_CONST(
            node, object))) {
        const struct json_litex_object_node_arg_t *p, *e;
        const struct json_litex_object_attr_t** a;

        a = &JSON_LITEX_NODE_CONST_CAST(node)->attr.object;
        if (!json_litex_lib_gen_object_attrs(lib, n.object, a))
            return false;

        for (p = n.object->args,
             e = p + n.object->size;
             p < e;
             p ++) {
            if (!json_litex_lib_gen_node_attrs(lib, p->val))
                return false;
        }
    }
    else
    if ((n.array = JSON_LITEX_NODE_AS_IF_CONST(
            node, array))) {
        const struct json_litex_array_attr_t** a;
        const struct json_litex_node_t **p, **e;

        a = &JSON_LITEX_NODE_CONST_CAST(node)->attr.array;
        if (!json_litex_lib_gen_array_attrs(lib, n.array, a))
            return false;

        for (p = n.array->args,
             e = p + n.array->size;
             p < e;
             p ++) {
            if (!json_litex_lib_gen_node_attrs(lib, *p))
                return false;
        }
    }
    else
        UNEXPECT_VAR("%d", node->type);

    return true;
}

#if 0 //!!!CHECK_ATTRS
static bool json_litex_lib_check_node_attrs(
    struct json_litex_lib_t* lib,
    const struct json_litex_node_t* node)
{
    union json_litex_node_pack_t n;

    if ((n.string = JSON_LITEX_NODE_AS_IF_CONST(
            node, string))) {
        // stev: place to do some extra checks...
    }
    else
    if ((n.object = JSON_LITEX_NODE_AS_IF_CONST(
            node, object))) {
        const struct json_litex_object_node_arg_t *p, *e;

        for (p = n.object->args,
             e = p + n.object->size;
             p < e;
             p ++) {
            if (!json_litex_lib_check_node_attrs(
                    lib, p->val))
                return false;
        }
    }
    else
    if ((n.array = JSON_LITEX_NODE_AS_IF_CONST(
            node, array))) {
        const struct json_litex_node_t **p, **e;

        for (p = n.array->args,
             e = p + n.array->size;
             p < e;
             p ++) {
            if (!json_litex_lib_check_node_attrs(
                    lib, *p))
                return false;
        }
    }
    else
        UNEXPECT_VAR("%d", node->type);

    return true;
}
#endif

static bool json_litex_lib_gen_def_attrs(
    struct json_litex_lib_t* lib,
    const struct json_litex_def_t* def)
{
    return json_litex_lib_gen_node_attrs(
        lib, def->node);
}

static bool json_litex_lib_check_def_attrs(
    struct json_litex_lib_t* lib,
    const struct json_litex_def_t* def)
{
#if 0 //!!!CHECK_ATTR
    return json_litex_lib_check_node_attrs(
        lib, def->node);
#else
    (void) lib;
    (void) def;
    return true;
#endif
}

static const struct json_litex_def_t*
    json_litex_lib_text_build(
        struct json_litex_lib_t* lib,
        struct json_litex_lib_text_impl_t* impl)
{
    const struct json_litex_node_t* n;
    struct json_litex_lib_obj_t o;
    size_t s;

    if (!JSON_LITEX_LIB_ERROR_IS(none))
        return NULL;

    if (impl->done_build)
        return lib->def;

    ASSERT(lib->def == NULL);

    if (impl->stack == NULL)
        impl->stack = json_litex_lib_stack_create(
            lib->sizes.ast.obj.stack_max,
            lib->sizes.ast.obj.stack_init);

    if (!json_litex_lib_text_validate_base(
            lib, impl, false))
        goto done;

    s = JSON_LITEX_LIB_STACK_SIZE();
    ENSURE(s == 1, "invalid path-lib rules stack: "
        "size is %zu", s);

    o = JSON_LITEX_LIB_STACK_POP();
    n = JSON_LITEX_LIB_OBJ_AS_IF_NODE(&o);

    ENSURE(n != NULL,
        "invalid path-lib rules stack: "
        "top element is not 'node'");
    impl->def.node = n;

    if (impl->expr == NULL)
        impl->expr = json_litex_expr_create(
              lib->opts.expr,
              JSON_LITEX_GEN_ATTR_OPTS_IS(flat_expr_impl)
            ? json_litex_expr_flat_impl_type
            : json_litex_expr_ast_impl_type,
             &lib->sizes.expr,
             (json_litex_expr_alloc_func_t)
              json_litex_lib_allocate,
             (json_litex_expr_dealloc_func_t)
              json_litex_lib_deallocate,
              lib);

    if (json_litex_lib_gen_def_attrs(lib, &impl->def)) {
        impl->def.rexes = json_litex_expr_get_rexes(
            impl->expr);
        lib->def = &impl->def;
    }

    json_litex_expr_destroy(impl->expr);
    impl->expr = NULL;

done:
    json_litex_lib_stack_destroy(impl->stack);
    impl->stack = NULL;

    impl->done_build = true;

    return lib->def;
}

static const struct json_litex_def_t*
    json_litex_lib_sobj_build(
        struct json_litex_lib_t* lib,
        struct json_litex_lib_sobj_impl_t* impl UNUSED)
{
    return JSON_LITEX_LIB_ERROR_IS(none) ? lib->def : NULL;
}

static const struct json_litex_def_t*
    json_litex_lib_build(struct json_litex_lib_t* lib)
{
    return lib->impl.build(lib, JSON_LITEX_LIB_IMPL_PTR());
}

enum json_litex_lib_spec_type_t
{
    json_litex_lib_text_spec_type,
    json_litex_lib_file_spec_type,
};

struct json_litex_lib_text_spec_t
{
    const uchar_t* def;
};

struct json_litex_lib_file_spec_t
{
    const char* name;
};

struct json_litex_lib_spec_t
{
    enum json_litex_lib_spec_type_t type;
    union {
        struct json_litex_lib_text_spec_t text;
        struct json_litex_lib_file_spec_t file;
    };
    const char* path_name;
};

static const char json_litex_text_name[] = "<text>";

static const struct json_litex_def_t*
    json_litex_lib_text_load(
        struct json_litex_lib_t* lib,
        struct json_litex_lib_text_impl_t* impl,
        const struct json_litex_lib_spec_t* spec)
{
    const struct json_litex_def_t* d;
    const uchar_t* b = NULL;
    const char* n;
    size_t s = 0;

    if (spec->type == json_litex_lib_text_spec_type) {
        b = spec->text.def;
        s = strulen(spec->text.def);
        n = json_litex_text_name;
    }
    else
    if (spec->type == json_litex_lib_file_spec_type) {
        if (!json_litex_lib_text_load_file(
                lib, impl, n = spec->file.name))
            goto error;

        ASSERT(impl->inited_buf);
        b = impl->buf.ptr;
        s = impl->buf.size;
    }
    else
        UNEXPECT_VAR("%d", spec->type);

    // stev: in case of litex lib errors, postpone
    // calling 'file_buf_done' until 'impl's life
    // time ends in 'json_litex_lib_text_impl_done'
    if (!json_litex_lib_text_parse_text(lib, impl, b, s, n) ||
        !(d = json_litex_lib_text_build(lib, impl)) ||
        !json_litex_lib_check_def_attrs(lib, d)) {
    error:
        lib->error.file_info.name = n;
        lib->error.file_info.buf = b;
        lib->error.file_info.size = s;
        return NULL;
    }

    if (spec->type == json_litex_lib_file_spec_type) {
        ASSERT(JSON_LITEX_LIB_ERROR_IS(none));
        ASSERT(impl->inited_buf);

        file_buf_done(&impl->buf);
        impl->inited_buf = false;
    }

    return d;
}

#define JSON_LITEX_LIB_LIB_ERROR__(t, e, n, l)          \
    ({                                                  \
        struct json_litex_lib_error_lib_t __e = {       \
            .type = json_litex_lib_error_ ## t ## _lib, \
            .lib.t = e,                                 \
            .name = n                                   \
        };                                              \
        JSON_LITEX_LIB_LIB_ERROR(__e, l);               \
        NULL;                                           \
    })
#define JSON_LITEX_LIB_LIB_ERROR_(t, e, n, l) \
        JSON_LITEX_LIB_LIB_ERROR__(t,         \
            json_litex_lib_ ## t ## _lib_error_ ## e, n, l)
#define JSON_LITEX_LIB_GENERIC_LIB_ERROR(e, l) \
        JSON_LITEX_LIB_LIB_ERROR_(generic, e, spec->path_name, l)
#define JSON_LITEX_LIB_SHARED_LIB_ERROR_CODE(e) \
        JSON_LITEX_LIB_LIB_ERROR__(shared, e, NULL, impl->lib_name)
#define JSON_LITEX_LIB_SHARED_LIB_ERROR(e) \
        JSON_LITEX_LIB_LIB_ERROR_(shared, e, NULL, impl->lib_name)

static enum json_litex_lib_shared_lib_error_type_t
    json_litex_lib_sobj_translate_dyn_lib_error(
        const struct dyn_lib_error_info_t* error)
{
#define SHR_ERR(n) json_litex_lib_shared_lib_error_ ## n
#define DYN_ERR(n) dyn_lib_error_ ## n

    STATIC(
        SHR_ERR(invalid_name) ==
        DYN_ERR(invalid_lib_name) + 0 &&

        SHR_ERR(load_lib_failed) ==
        DYN_ERR(load_lib_failed) + 0 &&

        SHR_ERR(get_version_sym_not_found) ==
        DYN_ERR(symbol_not_found) + 0 &&

        SHR_ERR(get_def_sym_not_found) ==
        DYN_ERR(symbol_not_found) + 1 &&

        SHR_ERR(wrong_lib_version) ==
        DYN_ERR(wrong_lib_version) + 1);

    enum json_litex_lib_shared_lib_error_type_t r =
        error->type;

    if (error->type == DYN_ERR(symbol_not_found)) {
        ASSERT(error->sym < 2);
        r += error->sym;
    }
    else
    if (error->type == DYN_ERR(wrong_lib_version))
        r ++;

    return r;

#undef DYN_ERR
#undef SHR_ERR
}

static const struct json_litex_def_t*
    json_litex_lib_sobj_load(
        struct json_litex_lib_t* lib,
        struct json_litex_lib_sobj_impl_t* impl)
{
    struct shared_lib_t
    {
        size_t (*get_version)(void);
        const struct json_litex_def_t*
            (*get_def)(void);
    };
#undef  CASE
#define CASE(n) DYN_LIB_ENTRY(shared_lib_t, n)
    static const struct dyn_lib_entry_t entries[] = {
        CASE(get_version),
        CASE(get_def)
    };
    static const struct dyn_lib_def_t def = {
        .ver_major = JSON_LITEX_VERSION_MAJOR,
        .ver_minor = JSON_LITEX_VERSION_MINOR,
        .n_entries = ARRAY_SIZE(entries),
        .entries = entries,
        .prefix = "litex"
    };
    struct dyn_lib_error_info_t e;
    struct shared_lib_t l;
    char* m = NULL;

    ASSERT(lib->def == NULL);
    ASSERT(impl->sobj == NULL);

    if (!dyn_lib_init(impl, impl->lib_name, &def, &l, &e,
            PRINT_DEBUG_COND ? &m : NULL)) {
        if (m != NULL) {
            PRINT_DEBUG("%s", m);
            free(m);
        }
        return JSON_LITEX_LIB_SHARED_LIB_ERROR_CODE(
            json_litex_lib_sobj_translate_dyn_lib_error(&e));
    }
    ASSERT(m == NULL);

    lib->def = l.get_def();
    if (lib->def == NULL)
        return JSON_LITEX_LIB_SHARED_LIB_ERROR(
            def_is_null);

    return lib->def;
}

static bool json_litex_lib_sobj_validate(
    struct json_litex_lib_t* lib,
    struct json_litex_lib_sobj_impl_t* impl)
{
    const struct json_litex_def_t* d;
    struct json_litex_ptr_space_t s;
    const void* p = NULL;
    bool r;

    d = json_litex_lib_sobj_build(lib, impl);
    if (d == NULL)
        return false;

    json_litex_ptr_space_init(&s,
        0, lib->sizes.ptr_space_size);
    r = json_litex_def_validate(d, &s, &p);
    json_litex_ptr_space_done(&s);

    if (!r) {
        ASSERT(p != NULL);

#ifdef JSON_DEBUG
        if (PRINT_DEBUG_COND) {
            Dl_info i;
            int r;

            memset(&i, 0, sizeof(i));
            r = dladdr(p, &i);

            PRINT_DEBUG_BEGIN_UNCOND(
                "validation failed on %p", p);
            if (r && i.dli_fname != NULL &&
                !strcmp(impl->lib_name, i.dli_fname) &&
                i.dli_fbase != NULL)
                print_debug_fmt(
                    " (offset=0x%tx)", p - i.dli_fbase);
            PRINT_DEBUG_END_UNCOND();
        }
#endif

        return JSON_LITEX_LIB_SHARED_LIB_ERROR(
            def_not_valid);
    }

    return true;
}

static bool json_litex_lib_is_shared_obj(
    struct json_litex_lib_t* lib,
    const char* lib_name)
{
    struct json_file_error_t e;
    bool r;

    STATIC(
        json_litex_lib_error_file_open_context + 1 ==
        json_file_open_error &&

        json_litex_lib_error_file_stat_context + 1 ==
        json_file_stat_error &&

        json_litex_lib_error_file_read_context + 1 ==
        json_file_read_error &&

        json_litex_lib_error_file_close_context + 1 ==
        json_file_close_error);

    r = json_file_is_shared_obj(lib_name, &e);

    if (e.type != json_file_none_error)
        return JSON_LITEX_LIB_SYS_ERROR(
            e.type - 1,
            e.sys);

    return r;
}

#define JSON_LITEX_OBJECT_NODE_LOOKUP_NAME(p, n)             \
    ({                                                       \
        ASSERT(JSON_LITEX_NODE_IS_CONST(p, object));         \
        ASSERT((p)->attr.object->lookup != NULL);            \
        (p)->attr.object->lookup((p)->attr.object->root, n); \
    })

static const struct json_litex_node_t*
    json_litex_lib_init_from_spec(
        struct json_litex_lib_t* lib,
        const struct json_litex_lib_spec_t* spec,
        const struct json_litex_lib_sizes_t* sizes,
        const struct json_litex_lib_opts_t* opts,
        bool load_lib)
{
    const struct json_litex_def_t* d;
#ifdef JSON_DEBUG
    struct timeval t0, t1;
#endif
    bool e = false;
    const char* l;

    JSON_LITEX_LIB_SIZES_VALIDATE(sizes);

    if (!load_lib)
        goto init_lib;

    if (spec->type == json_litex_lib_text_spec_type)
        l = json_litex_text_name;
    else
    if (spec->type == json_litex_lib_file_spec_type) {
        // stev: spec->file.name == NULL means stdin
        l = spec->file.name;

        e = l && json_litex_lib_is_shared_obj(lib, l);
        if (!JSON_LITEX_LIB_ERROR_IS(none))
            return NULL;
    }
    else
        UNEXPECT_VAR("%d", spec->type);

init_lib:
    if (!e)
        json_litex_lib_init_from_source_text(
            lib, sizes, opts);
    else
        json_litex_lib_init_from_shared_obj(
            lib, l, sizes, opts);

    if (!load_lib)
        return NULL;

#ifdef JSON_DEBUG
    if (lib->debug > 1)
        json_timeval_get_time(&t0);
#endif

    d = !e
        ? json_litex_lib_text_load(
            lib, JSON_LITEX_LIB_IMPL_AS(text), spec)
        : json_litex_lib_sobj_load(
            lib, JSON_LITEX_LIB_IMPL_AS(sobj));

#ifdef JSON_DEBUG
    if (lib->debug > 1) {
        json_timeval_get_time(&t1);
        PRINT_DEBUG_UNCOND(
            "path-lib loaded in %zu usecs",
            json_timeval_subtract(&t1, &t0));
    }
#endif

    if (d == NULL)
        return NULL;

    if (JSON_LITEX_NODE_IS_CONST(d->node, string) ||
        JSON_LITEX_NODE_IS_CONST(d->node, array)) {
        if (spec->path_name != NULL)
            return JSON_LITEX_LIB_GENERIC_LIB_ERROR(
                path_name_not_available, l);

        return d->node;
    }
    else
    if (JSON_LITEX_NODE_IS_CONST(d->node, object)) {
        const struct json_litex_node_t* n;

        if (spec->path_name == NULL)
            return d->node;

        if (!(n = JSON_LITEX_OBJECT_NODE_LOOKUP_NAME(
                d->node, PTR_UCHAR_CAST_CONST(
                    spec->path_name))))
            return JSON_LITEX_LIB_GENERIC_LIB_ERROR(
                path_name_not_found, l);

        return n;
    }
    else
        UNEXPECT_VAR("%d", d->node->type);
}

static bool json_litex_lib_print_struct(
    struct json_litex_lib_t* lib)
{
    const struct json_litex_def_t* d;

    d = json_litex_lib_build(lib);
    if (d == NULL)
        return false;

    json_litex_def_print(d, stdout);
    fputc('\n', stdout);

    return true;
}

static bool json_litex_lib_print_attr(
    struct json_litex_lib_t* lib)
{
    const struct json_litex_def_t* d;
    struct json_litex_ptr_space_t s;

    d = json_litex_lib_build(lib);
    if (d == NULL)
        return false;

    json_litex_ptr_space_init(&s,
        0, lib->sizes.ptr_space_size);
    json_litex_def_print_attr(d, &s, stdout);
    json_litex_ptr_space_done(&s);
    fputc('\n', stdout);

    return true;
}

#if 0 //!!!CHECK_ATTR
static bool json_litex_lib_check_attr(
    struct json_litex_lib_t* lib)
{
    const struct json_litex_def_t* d;

    d = json_litex_lib_build(lib);
    if (d == NULL)
        return false;

    return json_litex_lib_check_def_attrs(lib, d);
}
#endif

static bool json_litex_lib_gen_def(
    struct json_litex_lib_t* lib)
{
    const struct json_litex_def_t* d;
    struct json_litex_ptr_space_t s;

    d = json_litex_lib_build(lib);
    if (d == NULL)
        return false;

    if (JSON_LITEX_LIB_IMPL_IS(text) &&
        !json_litex_lib_check_def_attrs(lib, d))
        return false;

    json_litex_ptr_space_init(&s,
        lib->opts.gen_def, lib->sizes.ptr_space_size);
    json_litex_def_gen_def(d, &s, stdout);
    json_litex_ptr_space_done(&s);

    return true;
}

#undef  PRINT_DEBUG_COND
#define PRINT_DEBUG_COND obj->debug

struct json_litex_obj_sizes_t
{
    struct json_litex_expr_vm_sizes_t expr;
    struct json_litex_lib_sizes_t     lib;
};

#define JSON_LITEX_OBJ_SIZES_VALIDATE_(o)            \
    do {                                             \
        JSON_LITEX_EXPR_VM_SIZES_VALIDATE_(o expr.); \
        JSON_LITEX_LIB_SIZES_VALIDATE_(o lib.);      \
    } while (0)
#define JSON_LITEX_OBJ_SIZES_VALIDATE(p)             \
    do {                                             \
        STATIC(JSON_TYPEOF_IS_SIZES(p, litex_obj));  \
        JSON_LITEX_OBJ_SIZES_VALIDATE_(p->);         \
    } while (0)

enum json_litex_obj_error_type_t
{
    json_litex_obj_error_none,
    json_litex_obj_error_lib,
    json_litex_obj_error_path,
};

enum json_litex_obj_path_error_type_t
{
    json_litex_obj_path_error_none,
    json_litex_obj_path_error_unexpect_literal,
    json_litex_obj_path_error_path_stack_empty,
    json_litex_obj_path_error_expr_rexes_error,
    json_litex_obj_path_error_expr_exec_error,
    json_litex_obj_path_error_expr_falsified,
};

enum json_litex_obj_path_error_arg_type_t
{
    json_litex_obj_path_error_arg_none_type,
    json_litex_obj_path_error_arg_str_type,
    json_litex_obj_path_error_arg_expr_type
};

struct json_litex_obj_path_error_str_arg_t
{
    const struct json_litex_string_node_t* top;
};

struct json_litex_obj_path_error_expr_arg_t
{
    struct json_litex_expr_vm_error_t err;
};

struct json_litex_obj_path_error_arg_t
{
    enum json_litex_obj_path_error_arg_type_t type;
    union {
        // ...                                      none;
        struct json_litex_obj_path_error_str_arg_t  str;
        struct json_litex_obj_path_error_expr_arg_t expr;
    } val;
};

struct json_litex_obj_path_error_info_t
{
    enum json_litex_obj_path_error_type_t  type;
    struct json_litex_obj_path_error_arg_t arg;
    struct json_error_pos_t                pos;
};

struct json_litex_obj_error_info_t
{
    enum json_litex_obj_error_type_t            type;
    union {
        struct json_litex_lib_error_info_t      lib;
        struct json_litex_obj_path_error_info_t path;
    };
};

static void json_litex_print_path_repr(
    const uchar_t* str, size_t len, FILE* file)
{
    const uchar_t* q;

    while ((q = memchr(str, '/', len))) {
        size_t n = PTR_DIFF(q, str);

        pretty_print_repr(file, str, n, 0);
        fputs("//", file);

        ASSERT_SIZE_INC_NO_OVERFLOW(n);
        SIZE_SUB_EQ(len, n + 1);
        str = q + 1;
    }
    pretty_print_repr(file, str, len, 0);
}

static void json_litex_obj_path_error_str_arg_print_error_desc(
    const struct json_litex_obj_path_error_str_arg_t* arg,
    FILE* file)
{
    const struct json_litex_node_t *p, *s;
    const uchar_t *b, *e;
    size_t n = 0;

    ASSERT(arg->top != NULL);
    s = JSON_LITEX_TO_NODE_CONST(
        arg->top, string);

    for (p = s; p->parent; p = p->parent) {
        if (p->path == NULL)
            ASSERT(!JSON_LITEX_NODE_IS_CONST(
                p->parent, object));
        else {
            ASSERT(JSON_LITEX_NODE_IS_CONST(
                p->parent, object));
            n ++;
        }
    }

    fputs(": ", file);

    if (n == 0)
        fputs("root value", file);
    else {
        const uchar_t **t, **e;

        t = alloca(SIZE_MUL(n, sizeof(*t)));

        t += n;
        for (p = s; p->parent; p = p->parent) {
            if (p->path != NULL)
                *-- t = p->path->key.val;
        }

        fputs("value at \"/", file);
        for (e = t + n; t < e; t ++) {
            json_litex_print_path_repr(
                *t, strulen(*t),
                file);
            if (t < e - 1)
                fputc('/', file);
        }
        fputc('"', file);
    }

    for (n = strulen(arg->top->val),
         b = arg->top->val,
         e = b + n;
         b < e && ISSPACE(*b);
         b ++)
         n --;
    while (e > b && ISSPACE(e[-1]))
         n --, e --;

    fputs(" -> expression ", file);
    pretty_print_repr(file, b, n,
        PRETTY_PRINT_REPR_FLAGS(
            quote_non_print,
            print_quotes));
}

static void json_litex_obj_path_error_arg_print_error_desc(
    const struct json_litex_obj_path_error_arg_t* arg,
    FILE* file)
{
    switch (arg->type) {
    case json_litex_obj_path_error_arg_none_type:
        break;
    case json_litex_obj_path_error_arg_str_type:
        json_litex_obj_path_error_str_arg_print_error_desc(
            &arg->val.str, file);
        break;
    case json_litex_obj_path_error_arg_expr_type:
        fputs(": ", file);
        json_litex_expr_vm_error_print_desc(
            &arg->val.expr.err, file);
        break;
    default:
        UNEXPECT_VAR("%d", arg->type);
    }
}

static const char* json_litex_obj_path_error_type_get_desc(
    enum json_litex_obj_path_error_type_t type)
{
    switch (type) {
    case json_litex_obj_path_error_none:
        return "-";
    case json_litex_obj_path_error_unexpect_literal:
        return "unexpected JSON literal";
    case json_litex_obj_path_error_path_stack_empty:
        return "path stack is empty";
    case json_litex_obj_path_error_expr_rexes_error:
        return "expression regexes error";
    case json_litex_obj_path_error_expr_exec_error:
        return "expression exec error";
    case json_litex_obj_path_error_expr_falsified:
        return "expression falsified";
    default:
        UNEXPECT_VAR("%d", type);
    }
}

static void json_litex_obj_path_error_info_print_error_desc(
    const struct json_litex_obj_path_error_info_t* info,
    FILE* file)
{
    fputs(json_litex_obj_path_error_type_get_desc(
        info->type), file);
    json_litex_obj_path_error_arg_print_error_desc(
        &info->arg, file);
}

#ifdef JSON_DEBUG

#undef  CASE
#define CASE(n) \
    case json_litex_obj_path_error_ ## n: \
        return #n;

static const char* json_litex_obj_path_error_type_get_name(
    enum json_litex_obj_path_error_type_t type)
{
    switch (type) {
    CASE(none);
    CASE(unexpect_literal);
    CASE(path_stack_empty);
    CASE(expr_rexes_error);
    CASE(expr_exec_error);
    CASE(expr_falsified);
    default:
        UNEXPECT_VAR("%d", type);
    }
}

#endif

struct json_litex_obj_path_t
{
    const struct json_litex_node_t* top;
    size_t error;
    size_t size;
    bool last;
};

enum json_litex_obj_path_opts_t
{
    json_litex_obj_path_opts_ignore_err_lit = 1 << 1,

    json_litex_obj_path_opts_all =
    json_litex_obj_path_opts_ignore_err_lit
};

#define JSON_LITEX_OBJ_PATH_OPTS_(n) \
        json_litex_obj_path_opts_ ## n
#define JSON_LITEX_OBJ_PATH_OPTS(...) \
    (VA_ARGS_REPEAT(|, JSON_LITEX_OBJ_PATH_OPTS_, __VA_ARGS__))

#define JSON_LITEX_OBJ_PATH_OPTS_IS__(n) \
    (obj->opts.path & JSON_LITEX_OBJ_PATH_OPTS_(n))
#define JSON_LITEX_OBJ_PATH_OPTS_IS_(o, ...) \
    (VA_ARGS_REPEAT(o, JSON_LITEX_OBJ_PATH_OPTS_IS__, __VA_ARGS__))
#define JSON_LITEX_OBJ_PATH_OPTS_IS(...) \
        JSON_LITEX_OBJ_PATH_OPTS_IS_(&&, __VA_ARGS__)

struct json_litex_obj_opts_t
{
    enum json_litex_obj_path_opts_t path;
};

struct json_litex_obj_t
{
#ifdef JSON_DEBUG
    bits_t                             debug:
                                       debug_bits;
#endif
    struct json_litex_obj_opts_t       opts;
    struct json_handler_obj_t          hobj;
    struct json_litex_lib_t            lib;
    const struct json_litex_node_t*    root;
    struct json_litex_obj_path_t       path;
    struct json_litex_expr_vm_t        expr;
    struct json_litex_obj_error_info_t error;
};

#define JSON_LITEX_OBJ_CALL_HANDLER(n, ...) \
    ({                                      \
          !JSON_LITEX_OBJ_ERROR_IS(none)    \
        ? false                             \
        : obj->hobj.handler &&              \
          obj->hobj.handler->n ## _func     \
        ? obj->hobj.handler->n ## _func(    \
            obj->hobj.ptr,                  \
            ## __VA_ARGS__)                 \
        : true;                             \
    })

#define JSON_LITEX_OBJ_ERROR_IS(n) \
    (                              \
        obj->error.type ==         \
        json_litex_obj_error_ ## n \
    )

#define JSON_LITEX_OBJ_ERROR(t, e)      \
    ({                                  \
        obj->error.type =               \
            json_litex_obj_error_ ## t; \
        obj->error.t = e;               \
        false;                          \
    })

#define JSON_LITEX_OBJ_PATH_ERROR_(e) \
        JSON_LITEX_OBJ_ERROR(path, e)

#define JSON_LITEX_OBJ_PATH_ERROR__(t, e, v)            \
    ({                                                  \
        struct json_litex_obj_path_error_info_t __e = { \
            .type = json_litex_obj_path_error_ ## e,    \
            .arg = {                                    \
                .type = json_litex_obj_path_error_arg_  \
                    ## t ## _type,                      \
                v                                       \
            },                                          \
            .pos = {-1, -1}                             \
        };                                              \
        STATIC(                                         \
            json_litex_obj_path_error_ ## e !=          \
            json_litex_obj_path_error_none);            \
        JSON_LITEX_OBJ_PATH_ERROR_(__e);                \
    })

#define JSON_LITEX_OBJ_PATH_ERROR(e) \
        JSON_LITEX_OBJ_PATH_ERROR__(none, e, )
#define JSON_LITEX_OBJ_PATH_ERROR_ARG(t, e, a) \
        JSON_LITEX_OBJ_PATH_ERROR__(t, e, .val.t = a)

#define JSON_LITEX_OBJ_PATH_ERROR_STR(e, a)             \
    ({                                                  \
        struct json_litex_obj_path_error_str_arg_t __a; \
        __a.top = a;                                    \
        JSON_LITEX_OBJ_PATH_ERROR_ARG(str, e, __a);     \
    })
#define JSON_LITEX_OBJ_PATH_ERROR_EXPR(e)                \
    ({                                                   \
        struct json_litex_obj_path_error_expr_arg_t __a; \
        __a.err = obj->expr.error;                       \
        JSON_LITEX_OBJ_PATH_ERROR_ARG(expr, e, __a);     \
    })

#define JSON_LITEX_OBJ_TYPEOF_IS_PATH_ERROR(e) \
    TYPEOF_IS(e, struct json_litex_obj_path_error_info_t)

#define JSON_LITEX_OBJ_CHECK_PATH(t)                 \
    ({                                               \
        bool __r;                                    \
        JSON_LITEX_OBJ_CHECK_ ## t ## _DECL(__v);    \
        __r = json_litex_obj_check_path(obj, &__v);  \
        PRINT_DEBUG("path check: t=%s error=%s", #t, \
            json_litex_obj_path_error_type_get_name( \
                obj->error.path.type));              \
        __r;                                         \
    })

#define JSON_LITEX_OBJ_CHECK_NULL() \
        JSON_LITEX_OBJ_CHECK_PATH(NULL)
#define JSON_LITEX_OBJ_CHECK_BOOLEAN() \
        JSON_LITEX_OBJ_CHECK_PATH(BOOLEAN)
#define JSON_LITEX_OBJ_CHECK_NUMBER() \
        JSON_LITEX_OBJ_CHECK_PATH(NUMBER)
#define JSON_LITEX_OBJ_CHECK_STRING() \
        JSON_LITEX_OBJ_CHECK_PATH(STRING)

#define JSON_LITEX_OBJ_PATH_NODE_RESULT__(v) \
    do {                                     \
        err->arg.type =                      \
            json_litex_obj_path_error_arg_   \
            ## node_type;                    \
        err->arg.node = v;                   \
    } while (0)

static const uchar_t json_litex_null[] = "null";
static const uchar_t json_litex_false[] = "false";
static const uchar_t json_litex_true[] = "true";

#define JSON_LITEX_NODE_AS_IF_NODE_(q, p, n) \
    (                                        \
          JSON_LITEX_NODE_IS_(q, p, n)       \
        ? (p)                                \
        : JSON_LITEX_NODE_IS_(q, p, array)   \
        ? (p)->attr.array->n                 \
        : NULL                               \
    )

#define JSON_LITEX_NODE_AS_IF_STRING_(q, p) \
        JSON_LITEX_NODE_AS_IF_NODE_(q, p, string)
#define JSON_LITEX_NODE_AS_IF_STRING(p) \
        JSON_LITEX_NODE_AS_IF_STRING_(, p)
#define JSON_LITEX_NODE_AS_IF_STRING_CONST(p) \
        JSON_LITEX_NODE_AS_IF_STRING_(const, p)

#define JSON_LITEX_NODE_AS_IF_OBJECT_(q, p) \
        JSON_LITEX_NODE_AS_IF_NODE_(q, p, object)
#define JSON_LITEX_NODE_AS_IF_OBJECT(p) \
        JSON_LITEX_NODE_AS_IF_OBJECT_(, p)
#define JSON_LITEX_NODE_AS_IF_OBJECT_CONST(p) \
        JSON_LITEX_NODE_AS_IF_OBJECT_(const, p)

#ifdef JSON_DEBUG
#define JSON_LITEX_OBJ_PATH_PRINT_DEBUG_(t, f, ...) \
    do {                                            \
        const struct json_litex_node_t* __t =       \
            obj->path.top;                          \
        PRINT_DEBUG_BEGIN_COND(> 1, "%c%c%c" f,     \
            t, t, t, ## __VA_ARGS__);               \
        PRINT_DEBUG_FMT(" path={.top=[%p]", __t);   \
        if (__t != NULL) {                          \
            json_litex_node_print(__t, stderr);     \
            PRINT_DEBUG_FMT(" .parent=[%p]",        \
                __t->parent);                       \
            if (__t->parent != NULL)                \
                json_litex_node_print(              \
                    __t->parent, stderr);           \
        }                                           \
        PRINT_DEBUG_FMT(" .size=%zu .error=%zu}",   \
            obj->path.size, obj->path.error);       \
        PRINT_DEBUG_END();                          \
    } while (0)
#define JSON_LITEX_OBJ_PATH_PRINT_DEBUG(t, ...) \
        JSON_LITEX_OBJ_PATH_PRINT_DEBUG_(t, __VA_ARGS__)
#else // JSON_DEBUG
#define JSON_LITEX_OBJ_PATH_PRINT_DEBUG(t, ...) \
    do {} while (0)
#endif // JSON_DEBUG

static bool json_litex_obj_check_path(
    struct json_litex_obj_t* obj,
    const struct json_litex_value_t* val)
{
    const struct json_litex_string_node_t* s;
    const struct json_litex_node_t* n;
    json_litex_expr_value_t r;

    JSON_LITEX_OBJ_PATH_PRINT_DEBUG('>');

    ASSERT(obj->path.top != NULL);

    if (obj->path.error ||
        !(n = JSON_LITEX_NODE_AS_IF_STRING_CONST(
            obj->path.top))) {
        if (!JSON_LITEX_OBJ_PATH_OPTS_IS(
                ignore_err_lit))
            return JSON_LITEX_OBJ_PATH_ERROR(
                unexpect_literal);

        return true;
    }

    if (!json_litex_expr_vm_execute(
            &obj->expr, n->attr.string, val, &r))
        return JSON_LITEX_OBJ_PATH_ERROR_EXPR(
            expr_exec_error);
    if (!r) {
        s = JSON_LITEX_NODE_AS_CONST(n, string);
        return JSON_LITEX_OBJ_PATH_ERROR_STR(
            expr_falsified, s);
    }

    return true;
}

static bool json_litex_obj_pop_path(
    struct json_litex_obj_t* obj)
{
    JSON_LITEX_OBJ_PATH_PRINT_DEBUG('>');

    if (obj->path.size == 0)
        return JSON_LITEX_OBJ_PATH_ERROR(
            path_stack_empty);

    obj->path.size --;

    if (obj->path.error)
        obj->path.error --;
    else {
        ASSERT(obj->path.top != NULL);
        ASSERT(obj->path.top->parent != NULL);
        obj->path.top = obj->path.top->parent;

        if (obj->path.top->parent &&
            JSON_LITEX_NODE_IS_CONST(
                obj->path.top->parent, array))
            obj->path.top = obj->path.top->parent;
    }

    JSON_LITEX_OBJ_PATH_PRINT_DEBUG('<');
    return true;
}

static bool json_litex_obj_push_path(
    struct json_litex_obj_t* obj,
    const uchar_t* name)
{
    const struct json_litex_node_t *t, *n;

    JSON_LITEX_OBJ_PATH_PRINT_DEBUG(
        '>', " name='%s'", name);

    ASSERT(obj->path.top != NULL);

    obj->path.size ++;

    if (obj->path.error ||
        !(t = JSON_LITEX_NODE_AS_IF_OBJECT_CONST(
                obj->path.top)) ||
        !(n = JSON_LITEX_OBJECT_NODE_LOOKUP_NAME(
                t, name))) {
        obj->path.error ++;
        return true;
    }

    obj->path.top = n;

    JSON_LITEX_OBJ_PATH_PRINT_DEBUG(
        '<', " name='%s'", name);
    return true;
}

static bool json_litex_obj_pop_last(
    struct json_litex_obj_t* obj)
{
    if (obj->path.last &&
        !json_litex_obj_pop_path(obj))
        return false;
    else {
        obj->path.last = true;
        return true;
    }
}

static void json_litex_obj_push_last(
    struct json_litex_obj_t* obj)
{
    ASSERT(obj->path.last);
    obj->path.last = false;
}

static bool json_litex_obj_set_last(
    struct json_litex_obj_t* obj,
    const uchar_t* name)
{
    return
        json_litex_obj_pop_last(obj) &&
        json_litex_obj_push_path(obj, name);
}

#ifdef JSON_DEBUG
#define JSON_LITEX_OBJ_PRINT_DEBUG_(v, l, o)   \
    do {                                       \
        PRINT_DEBUG_BEGIN("val=");             \
        pretty_print_string(stderr, v, l, o);  \
        PRINT_DEBUG_FMT(" path={.top=");       \
        json_litex_node_print(                 \
            obj->path.top, stderr);            \
        PRINT_DEBUG_FMT(                       \
            " .size=%zu .error=%zu .last=%d}", \
            obj->path.size, obj->path.error,   \
            obj->path.last);                   \
        PRINT_DEBUG_END();                     \
    } while (0)
#define JSON_LITEX_OBJ_PRINT_DEBUG_NULL()      \
        JSON_LITEX_OBJ_PRINT_DEBUG_(           \
            json_litex_null,                   \
            ARRAY_SIZE(json_litex_null) - 1,   \
            0)
#define JSON_LITEX_OBJ_PRINT_DEBUG_BOOLEAN()        \
        JSON_LITEX_OBJ_PRINT_DEBUG_(                \
            val ? json_litex_true                   \
                : json_litex_false,                 \
            val ? ARRAY_SIZE(json_litex_true) - 1   \
                : ARRAY_SIZE(json_litex_false) - 1, \
            0)
#define JSON_LITEX_OBJ_PRINT_DEBUG_NUMBER() \
        JSON_LITEX_OBJ_PRINT_DEBUG_(val, len, 0)
#define JSON_LITEX_OBJ_PRINT_DEBUG_STRING() \
        JSON_LITEX_OBJ_PRINT_DEBUG_(val, len,   \
            pretty_print_string_quotes)
#else
#define JSON_LITEX_OBJ_PRINT_DEBUG_NULL() \
    do {} while (0)
#define JSON_LITEX_OBJ_PRINT_DEBUG_BOOLEAN() \
    do {} while (0)
#define JSON_LITEX_OBJ_PRINT_DEBUG_NUMBER() \
    do {} while (0)
#define JSON_LITEX_OBJ_PRINT_DEBUG_STRING() \
    do {} while (0)
#endif

#define JSON_LITEX_OBJ_CHECK_DECL_(n, t, ...)    \
    const struct json_litex_value_t n = {        \
        .type = json_litex_value_ ## t ## _type, \
        ## __VA_ARGS__                           \
    }
#define JSON_LITEX_OBJ_CHECK_NULL_DECL(n) \
        JSON_LITEX_OBJ_CHECK_DECL_(n, null, )
#define JSON_LITEX_OBJ_CHECK_BOOLEAN_DECL(n) \
        JSON_LITEX_OBJ_CHECK_DECL_( \
            n, boolean, .u.boolean = val)
#define JSON_LITEX_OBJ_CHECK_STRING_DECL_(n, t) \
        JSON_LITEX_OBJ_CHECK_DECL_( \
            n, t, .u.t = { .ptr = val, .len = len })
#define JSON_LITEX_OBJ_CHECK_NUMBER_DECL(n) \
        JSON_LITEX_OBJ_CHECK_STRING_DECL_(n, number)
#define JSON_LITEX_OBJ_CHECK_STRING_DECL(n) \
        JSON_LITEX_OBJ_CHECK_STRING_DECL_(n, string)

#define CALL_HANDLER JSON_LITEX_OBJ_CALL_HANDLER

static bool json_litex_obj_null(
    struct json_litex_obj_t* obj)
{
    JSON_LITEX_OBJ_PRINT_DEBUG_NULL();

    if (!JSON_LITEX_OBJ_CHECK_NULL())
        return false;

    return CALL_HANDLER(null);
}

static bool json_litex_obj_boolean(
    struct json_litex_obj_t* obj, bool val)
{
    JSON_LITEX_OBJ_PRINT_DEBUG_BOOLEAN();

    if (!JSON_LITEX_OBJ_CHECK_BOOLEAN())
        return false;

    return CALL_HANDLER(boolean, val);
}

static bool json_litex_obj_number(
    struct json_litex_obj_t* obj,
    const uchar_t* val,
    size_t len)
{
    JSON_LITEX_OBJ_PRINT_DEBUG_NUMBER();

    if (!JSON_LITEX_OBJ_CHECK_NUMBER())
        return false;

    return CALL_HANDLER(number, val, len);
}

static bool json_litex_obj_string(
    struct json_litex_obj_t* obj,
    const uchar_t* val,
    size_t len)
{
    JSON_LITEX_OBJ_PRINT_DEBUG_STRING();

    if (!JSON_LITEX_OBJ_CHECK_STRING())
        return false;

    return CALL_HANDLER(string, val, len);
}

static bool json_litex_obj_object_start(
    struct json_litex_obj_t* obj)
{
    json_litex_obj_push_last(obj);
    return CALL_HANDLER(object_start);
}

static bool json_litex_obj_object_key(
    struct json_litex_obj_t* obj,
    const uchar_t* key,
    size_t len)
{
    if (!json_litex_obj_set_last(obj, key))
        return false;

    return CALL_HANDLER(object_key, key, len);
}

static bool json_litex_obj_object_sep(
    struct json_litex_obj_t* obj)
{ return CALL_HANDLER(object_sep); }

static bool json_litex_obj_object_end(
    struct json_litex_obj_t* obj)
{
    if (!json_litex_obj_pop_last(obj))
        return false;

    return CALL_HANDLER(object_end);
}

static bool json_litex_obj_array_start(
    struct json_litex_obj_t* obj)
{ return CALL_HANDLER(array_start); }

static bool json_litex_obj_array_sep(
    struct json_litex_obj_t* obj)
{ return CALL_HANDLER(array_sep); }

static bool json_litex_obj_array_end(
    struct json_litex_obj_t* obj)
{ return CALL_HANDLER(array_end); }

static bool json_litex_obj_value_sep(
    struct json_litex_obj_t* obj)
{ return CALL_HANDLER(value_sep); }

#undef CALL_HANDLER

static bool json_litex_obj_init(
    struct json_litex_obj_t* obj,
    const struct json_handler_obj_t* hobj,
    const struct json_litex_lib_spec_t* spec,
    const struct json_litex_obj_sizes_t* sizes,
    const struct json_litex_lib_opts_t* lib_opts,
    const struct json_litex_obj_opts_t* opts)
{
    JSON_LITEX_OBJ_SIZES_VALIDATE(sizes);

    memset(obj, 0, sizeof(struct json_litex_obj_t));

#ifdef JSON_DEBUG
    obj->debug = SIZE_TRUNC_BITS(
        json_litex_debug_get_level(
            json_litex_debug_obj_class),
        debug_bits);
#endif

    obj->hobj = *hobj;
    obj->opts = *opts;

    json_litex_expr_vm_init(
        &obj->expr, &sizes->expr);

    obj->root = json_litex_lib_init_from_spec(
        &obj->lib, spec, &sizes->lib, lib_opts, true);

    if (obj->root != NULL) {
        ASSERT(obj->lib.error.type ==
                    json_litex_lib_error_none);
        obj->path.top = obj->root;
        obj->path.last = true;
    }
    else {
        ASSERT(obj->lib.error.type !=
                    json_litex_lib_error_none);
        return JSON_LITEX_OBJ_ERROR(
            lib, obj->lib.error);
    }

    if (!json_litex_expr_vm_set_rexes(
            &obj->expr, obj->lib.def->rexes))
        return JSON_LITEX_OBJ_PATH_ERROR_EXPR(
            expr_rexes_error);

    JSON_LITEX_OBJ_PATH_PRINT_DEBUG('<');

    return true;
}

static void json_litex_obj_done(
    struct json_litex_obj_t* obj)
{
    json_litex_expr_vm_done(&obj->expr);
    json_litex_lib_done(&obj->lib);
}

static bool json_litex_obj_get_is_error(
    struct json_litex_obj_t* obj)
{
    return !JSON_LITEX_OBJ_ERROR_IS(none);
}

static const char* json_litex_obj_error_type_get_desc(
    enum json_litex_obj_error_type_t type)
{
    switch (type) {
    case json_litex_obj_error_none:
        return "none";
    case json_litex_obj_error_lib:
        return "path lib";
    case json_litex_obj_error_path:
        return "path check";
    default:
        UNEXPECT_VAR("%d", type);
    }
}

static struct json_error_pos_t
    json_litex_obj_error_info_get_pos(
        const struct json_litex_obj_error_info_t* info)
{
    switch (info->type) {
    case json_litex_obj_error_none:
        return (struct json_error_pos_t) {0, 0};
    case json_litex_obj_error_lib:
        return json_litex_lib_error_info_get_pos(
            &info->lib);
    case json_litex_obj_error_path:
        return info->path.pos;
    default:
        UNEXPECT_VAR("%d", info->type);
    }
}

static const struct json_file_info_t*
    json_litex_obj_error_info_get_file(
        const struct json_litex_obj_error_info_t* info)
{
    switch (info->type) {
    case json_litex_obj_error_none:
    case json_litex_obj_error_path:
        return NULL;
    case json_litex_obj_error_lib:
        return json_litex_lib_error_info_get_file(
            &info->lib);
    default:
        UNEXPECT_VAR("%d", info->type);
    }
}

static void json_litex_obj_error_info_print_error_desc(
    const struct json_litex_obj_error_info_t* info,
    FILE* file)
{
    switch (info->type) {
    case json_litex_obj_error_none:
        fputc('-', file);
        break;
    case json_litex_obj_error_lib:
        json_litex_lib_error_info_print_error_desc(
            &info->lib, file);
        break;
    case json_litex_obj_error_path:
        json_litex_obj_path_error_info_print_error_desc(
            &info->path, file);
        break;
    default:
        UNEXPECT_VAR("%d", info->type);
    }
}

static struct json_error_pos_t
    json_litex_obj_get_error_pos(
        struct json_litex_obj_t* obj)
{
    return json_litex_obj_error_info_get_pos(
        &obj->error);
}

static const struct json_file_info_t*
    json_litex_obj_get_error_file(
        struct json_litex_obj_t* obj)
{
    return json_litex_obj_error_info_get_file(
        &obj->error);
}

static void json_litex_obj_print_error_desc(
    struct json_litex_obj_t* obj,
    FILE* file)
{
    fprintf(file, "%s error: ",
        json_litex_obj_error_type_get_desc(
            obj->error.type));
    json_litex_obj_error_info_print_error_desc(
        &obj->error, file);
}

enum json_litex_opts_error_type_t
{
    json_litex_opts_error_type_invalid_opt_arg,
    json_litex_opts_error_type_illegal_opt_arg,
    json_litex_opts_error_type_missing_opt_arg_str,
    json_litex_opts_error_type_missing_opt_arg_ch,
    json_litex_opts_error_type_not_allowed_opt_arg,
    json_litex_opts_error_type_invalid_opt_str,
    json_litex_opts_error_type_invalid_opt_ch,
};

struct json_litex_opts_error_opt_arg_t
{
    const char* name;
    const char* arg;
};

struct json_litex_opts_error_opt_str_t
{
    const char* name;
};

struct json_litex_opts_error_opt_ch_t
{
    char name;
};

struct json_litex_opts_error_t
{
    enum json_litex_opts_error_type_t type;
    union {
        struct json_litex_opts_error_opt_arg_t
               invalid_opt_arg;
        struct json_litex_opts_error_opt_arg_t
               illegal_opt_arg;
        struct json_litex_opts_error_opt_str_t
               missing_opt_arg_str;
        struct json_litex_opts_error_opt_ch_t
               missing_opt_arg_ch;
        struct json_litex_opts_error_opt_str_t
               not_allowed_opt_arg;
        struct json_litex_opts_error_opt_str_t
               invalid_opt_str;
        struct json_litex_opts_error_opt_ch_t
               invalid_opt_ch;
    };
};

#define JSON_LITEX_OPTS_ERROR(t, n)           \
    ({                                        \
        err->type =                           \
            json_litex_opts_error_type_ ## t; \
        err->t.name = n;                      \
        false;                                \
    })
#define JSON_LITEX_OPTS_ERROR_ARG(t, n, v)    \
    ({                                        \
        err->type =                           \
            json_litex_opts_error_type_ ## t; \
        err->t.name = n;                      \
        err->t.arg = v;                       \
        false;                                \
    })

enum json_litex_opts_action_t
{
    json_litex_opts_action_none,
    json_litex_opts_action_validate,
    json_litex_opts_action_print_struct,
    json_litex_opts_action_print_attr,
    //!!!CHECK_ATTR json_litex_opts_action_check_attr,
    json_litex_opts_action_gen_def
};

enum json_litex_opts_path_type_t
{
    json_litex_opts_path_type_def,
    json_litex_opts_path_type_lib
};

enum json_litex_opts_expr_compiler_t
{
    json_litex_opts_expr_compiler_default,
    json_litex_opts_expr_compiler_flat,
    json_litex_opts_expr_compiler_ast
};

enum json_litex_opts_unexpect_lit_t
{
    json_litex_opts_unexpect_lit_error,
    json_litex_opts_unexpect_lit_ignore,
};

enum json_litex_opts_trie_path_t
{
    json_litex_opts_trie_path_expanded,
    json_litex_opts_trie_path_function,
};

enum json_litex_opts_path_eq_t
{
    json_litex_opts_path_eq_builtin,
    json_litex_opts_path_eq_strcmp,
};

struct json_litex_opts_t
{
    enum json_litex_opts_action_t
                         action;
    enum json_litex_opts_path_type_t
                         path_type;
    const char          *path_value;
    const char          *path_name;
    enum json_litex_opts_expr_compiler_t
                         expr_compiler;
    enum json_litex_opts_unexpect_lit_t
                         unexpect_lit;
    enum json_litex_opts_trie_path_t
                         trie_path;
    enum json_litex_opts_path_eq_t
                         path_eq;

#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def) \
    size_t               sizes_ ## id;
#include "json-litex-sizes.def"

    bits_t               dump: 1;
    bits_t               usage: 1;
    bits_t               usage_sizes: 1;
    bits_t               version: 1;

#ifdef JSON_DEBUG
    bits_t               debug_expr: debug_bits;
    bits_t               debug_lib:  debug_bits;
    bits_t               debug_obj:  debug_bits;
#endif

    size_t               argc;
    char* const         *argv;
};

#define FILTER_VERSION VERSION_ID(JSON_FILTER)

static void json_litex_version(void)
{
    fprintf(stdout,
        "%s: version %s\n"
        "%s: json filter version: %s\n\n%s",
        program, verdate, program,
        STRINGIFY(FILTER_VERSION),
        license);
}

#define PARSE_SIZE_OPTARG_(n)   json_litex_opts_parse_ ## n ## _optarg

#define PARSE_BUF_SIZE_OPTARG   PARSE_SIZE_OPTARG_(su_size)
#define PARSE_STACK_SIZE_OPTARG PARSE_SIZE_OPTARG_(su_size)
#define PARSE_POOL_SIZE_OPTARG  PARSE_SIZE_OPTARG_(su_size)
#define PARSE_PLAIN_SIZE_OPTARG PARSE_SIZE_OPTARG_(size)

#define PARSE_SIZE_OPTARG(id, name, type, min, max) \
    PARSE_ ## type ## _SIZE_OPTARG(                 \
        "sizes-" name, optarg, min, max, err,       \
        &opts->sizes_ ## id)

#define SU_SIZE_(n) \
    struct su_size_t n ## _su = su_size(opts->n);

#define SU_BUF_SIZE      SU_SIZE_
#define SU_STACK_SIZE    SU_SIZE_
#define SU_POOL_SIZE     SU_SIZE_
#define SU_PLAIN_SIZE(n)

#define SU_SIZE(id, type) SU_ ## type ## _SIZE(sizes_ ## id)

#define FMT_SU_SIZE    "%zu%s"
#define FMT_BUF_SIZE   FMT_SU_SIZE
#define FMT_STACK_SIZE FMT_SU_SIZE
#define FMT_POOL_SIZE  FMT_SU_SIZE
#define FMT_PLAIN_SIZE "%zu"

#define FMT_SIZE(type) FMT_ ## type ## _SIZE

#define ARG_SU_SIZE(n)    n ## _su.sz, n ## _su.su
#define ARG_BUF_SIZE      ARG_SU_SIZE
#define ARG_STACK_SIZE    ARG_SU_SIZE
#define ARG_POOL_SIZE     ARG_SU_SIZE
#define ARG_PLAIN_SIZE(n) opts->n

#define ARG_SIZE(id, type) ARG_ ## type ## _SIZE(sizes_ ## id),

static void json_litex_usage(void)
{
    fprintf(stdout,
        "usage: %s [ACTION|OPTION]... [FILE]\n"
        "where actions are specified as:\n"
        "  -V|--validate                check the validity of input (default)\n"
        "  -P|--print-struct            print out the constructed structure\n"
        "  -A|--print-attr              print out the constructed structure\n"
        "                                 along with generated attributes\n"
#if 0 //!!!CHECK_ATTR
        "  -C|--check-attr              check the constructed structure for to\n"
        "                                 validate the generated attributes\n"
#endif
        "  -D|--gen-def                 generate the litex def code for the\n"
        "                                 constructed structure\n"
        "the options are:\n"
        "  -d|--path-def STR            take the input path structure in form of text\n"
        "  -p|--path-lib FILE[:NAME]      string specified by STR, or in form of text\n"
        "  -n|--path-name [NAME]          file or compiled library specified by FILE;\n"
        "                                 the optional NAME specifies which structure\n"
        "                                 to use in case the def/library is made of a\n"
        "                                 set of inner structures; if neither options\n"
        "                                 `-d|--path-def' nor `-p|--path-lib' nor a\n"
        "                                 standalone FILE argument were given then\n"
        "                                 take the input path structure from stdin;\n"
        "                                 note that when the library runs in filter\n"
        "                                 mode input cannot come from stdin\n"
        "  -c|--[expr-]compiler NAME    when compiling literal expressions to internal\n"
        "                                 executable code, use the named expression\n"
        "                                 compiler: flat or ast; the flat compiler is\n"
        "                                 faster compared to the other one, while the\n"
        "                                 ast compiler produces better executable code;\n"
        "                                 which compiler is used by default depends on\n"
        "                                 path library's running mode: in filter mode,\n"
        "                                 that is flat; in exec mode is ast; note that\n"
        "                                 all literal expressions of a compiled path\n"
        "                                 library are already compiled: therefore this\n"
        "                                 option has no influence on running compiled\n"
        "                                 path libraries\n"
        "  -l|--unexpect-literals ACT   specify program's behavior when running into\n"
        "                                 unexpected JSON literals; ACT can be either\n"
        "                                 'error' for terminating the execution with\n"
        "                                 error, or 'ignore' for ignoring the literal;\n"
        "                                 the default action is 'error'; option `-l'\n"
        "                                 cuts short `--unexpect-literals=ignore'\n"
        "  -t|--trie-path TYPE          when generating trie lookup functions produce\n"
        "                                 trie path matching code of specified type:\n"
        "                                 expanded or function (default)\n"
        "  -q|--path-equality TYPE      when given `-t|--trie-path=function', do use the\n"
        "                                 specified type of path equality matching code:\n"
        "                                 either 'strcmp' for the named library function\n"
        "                                 or, otherwise, 'builtin' for using a builtin\n"
        "                                 function (default)\n"
        "     --sizes-NAME VALUE        assign the named sizes parameter the given value;\n"
        "                                 use `--help-sizes' option to obtain the list of\n"
        "                                 all the sizes parameters along with the minimum,\n"
        "                                 the maximum and the default values and a short\n"
        "                                 description attached\n"
#ifdef JSON_DEBUG
        "     --debug-CLASS [LEVEL]     do print some debugging output for the named\n"
        "                                 class, which can be any of: expr, lib or obj;\n"
        "                                 the level can be [0-9], 1 being the default\n"
        "     --no-debug                do not print debugging output at all (default)\n"
#endif
        "     --dump-options            print parsed options and exit\n"
        "     --help-sizes              print info about the sizes parameters and exit\n"
        "  -v|--version                 print version numbers and exit\n"
        "  -?|--help                    display this help info and exit\n",
        program);
}

#define NAME_(x, t)                  \
    ({                               \
        size_t __v = opts->x;        \
        ARRAY_NON_NULL_ELEM(t, __v); \
    })
#define NAME(x)  NAME_(x, x ## s)
#define NNUL(X)  (opts->X ? opts->X : "-")
#define NOYES(x) NAME_(x, noyes)

static void json_litex_dump_opts(
    const struct json_litex_opts_t* opts)
{
#undef  CASE
#define CASE(n, v)   [json_litex_opts_action_ ## n] = v
#define CASE2(n1, n2) CASE(n1 ## _ ## n2, #n1 "-" #n2)
#define CASE1(n)      CASE(n, #n)
    static const char* actions[] = {
        CASE1(validate),
        CASE2(print, struct),
        CASE2(print, attr),
        //!!!CHECK_ATTR CASE2(check, attr),
        CASE2(gen, def)
    };
#undef  CASE
#define CASE(n) [json_litex_opts_path_type_ ## n] = #n
    static const char* path_types[] = {
        CASE(def),
        CASE(lib)
    };
#undef  CASE
#define CASE(n) [json_litex_opts_expr_compiler_ ## n] = #n
    static const char* expr_compilers[] = {
        CASE(default),
        CASE(flat),
        CASE(ast)
    };
#undef  CASE
#define CASE(n) [json_litex_opts_unexpect_lit_ ## n] = #n
    static const char* unexpect_lits[] = {
        CASE(error),
        CASE(ignore),
    };
#undef  CASE
#define CASE(n) [json_litex_opts_trie_path_ ## n] = #n
    static const char* trie_paths[] = {
        CASE(expanded),
        CASE(function)
    };
#undef  CASE
#define CASE(n) [json_litex_opts_path_eq_ ## n] = #n
    static const char* path_eqs[] = {
        CASE(builtin),
        CASE(strcmp)
    };
#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def) \
    SU_SIZE(id, type)
#include "json-litex-sizes.def"

    fprintf(stdout,
        "action:                         %s\n"
        "path-type:                      %s\n"
        "path-%3s:                       %s\n"
        "path-name:                      %s\n"
        "expr-compiler:                  %s\n"
        "unexpect-literals:              %s\n"
        "trie-path:                      %s\n"
        "path-equality:                  %s\n"
#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def) \
        "sizes-" name ":" pad " " FMT_SIZE(type) "\n"
#include "json-litex-sizes.def"
#ifdef JSON_DEBUG
        "debug-expr:                     %d\n"
        "debug-lib:                      %d\n"
        "debug-obj:                      %d\n"
#endif
        "argc:                           %zu\n",
        NAME(action),
        NAME(path_type),
        NAME(path_type),
        NNUL(path_value),
        NNUL(path_name),
        NAME(expr_compiler),
        NAME(unexpect_lit),
        NAME(trie_path),
        NAME(path_eq),
#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def) \
        ARG_SIZE(id, type)
#include "json-litex-sizes.def"
#ifdef JSON_DEBUG
        opts->debug_expr,
        opts->debug_lib,
        opts->debug_obj,
#endif
        opts->argc);

    pretty_print_strings(stdout,
        PTR_CONST_PTR_CAST(opts->argv, char),
        opts->argc, "argv", 32, 0);
}

static void json_litex_usage_sizes(void)
{
    enum {
        BUF   = su_size_param_su,
        STACK = su_size_param_su,
        POOL  = su_size_param_su,
        PLAIN = su_size_param_plain,
    };
    static const struct su_size_param_t params[] = {
#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def) \
        { type, name, desc, min, max, def },
#include "json-litex-sizes.def"
    };

    su_size_print_sizes(params, ARRAY_SIZE(params), stdout);
}

static bool json_litex_opts_is_info(
    const struct json_litex_opts_t* opts)
{
    return
        opts->dump ||
        opts->usage ||
        opts->usage_sizes ||
        opts->version;
}

static bool json_litex_opts_print_info(
    const struct json_litex_opts_t* opts)
{
    if (opts->version)
        json_litex_version();
    if (opts->dump)
        json_litex_dump_opts(opts);
    if (opts->usage)
        json_litex_usage();
    if (opts->usage_sizes)
        json_litex_usage_sizes();

    return json_litex_opts_is_info(opts);
}

#define opt_error JSON_LITEX_OPTS_ERROR
#define arg_error JSON_LITEX_OPTS_ERROR_ARG

#define invalid_opt_arg(o, a)  arg_error(invalid_opt_arg, o, a)
#define illegal_opt_arg(o, a)  arg_error(illegal_opt_arg, o, a)
#define missing_opt_arg_str(o) opt_error(missing_opt_arg_str, o)
#define missing_opt_arg_ch(o)  opt_error(missing_opt_arg_ch, o)
#define not_allowed_opt_arg(o) opt_error(not_allowed_opt_arg, o)
#define invalid_opt_str(o)     opt_error(invalid_opt_str, o)
#define invalid_opt_ch(o)      opt_error(invalid_opt_ch, o)

#define SU_SIZE_OPT_NAME       json_litex_opts
#define SU_SIZE_OPT_ERROR_TYPE struct json_litex_opts_error_t

#define SU_SIZE_OPT_ERROR_CASE(n)                \
    case su_size_parse_error_ ## n ## _num:      \
        return n ## _opt_arg(opt_name, opt_arg);

#define SU_SIZE_OPT_NEED_PARSE_SIZE
#define SU_SIZE_OPT_NEED_PARSE_SIZE_SU
#define SU_SIZE_OPT_NEED_STATIC_FUNCS
#define SU_SIZE_OPT_NEED_BOOL_FUNCS
#include "su-size-opt-impl.h"

static bool json_litex_opts_parse_path_lib_optarg(
    const char* opt_name, char* opt_arg,
    struct json_litex_opts_error_t* err,
    const char** path_value,
    const char** path_name)
{
    char* p = opt_arg;

    *path_value = opt_arg;

    if ((*p == '\0') || (*p ++ == '-' && *p == '\0'))
        return illegal_opt_arg(opt_name, opt_arg);

    if ((p = strrchr(opt_arg, ':'))) {
        if (p[1] == '\0')
            return illegal_opt_arg(opt_name, opt_arg);
        *p ++ = 0;
        *path_name = p;
    }

    return true;
}

// $ print() { printf '%s\n' "$@"; }
// $ print 'flat =json_litex_opts_expr_compiler_flat' 'ast =json_litex_opts_expr_compiler_ast'|gen-func -f json_litex_opts_lookup_expr_compiler_name -Pf -q \!strcmp|ssed -R '1s/^/static /;1s/(?<=\()/\n\t/;1s/\bresult_t\&/enum json_litex_opts_expr_compiler_t*/;s/(?=t =)/*/;s/result_t:://'

static bool json_litex_opts_lookup_expr_compiler_name(
    const char* n, enum json_litex_opts_expr_compiler_t* t)
{
    // pattern: ast|flat
    switch (*n ++) {
    case 'a':
        if (!strcmp(n, "st")) {
            *t = json_litex_opts_expr_compiler_ast;
            return true;
        }
        return false;
    case 'f':
        if (!strcmp(n, "lat")) {
            *t = json_litex_opts_expr_compiler_flat;
            return true;
        }
    }
    return false;
}

static bool json_litex_opts_parse_expr_compiler_optarg(
    const char* opt_name, char* opt_arg,
    struct json_litex_opts_error_t* err,
    enum json_litex_opts_expr_compiler_t* compiler)
{
    if (!json_litex_opts_lookup_expr_compiler_name(
            opt_arg, compiler))
        return invalid_opt_arg(
            opt_name,
            opt_arg);

    return true;
}

// $ print 'error =json_litex_opts_unexpect_lit_error' 'ignore =json_litex_opts_unexpect_lit_ignore'|gen-func -f json_litex_opts_lookup_unexpect_lit_name -Pf -q \!strcmp|ssed -R '1s/^/static /;1s/(?<=\()/\n\t/;1s/\bresult_t\&/enum json_litex_opts_unexpect_lit_t*/;s/(?=t =)/*/;s/result_t:://'

static bool json_litex_opts_lookup_unexpect_lit_name(
    const char* n, enum json_litex_opts_unexpect_lit_t* t)
{
    // pattern: error|ignore
    switch (*n ++) {
    case 'e':
        if (!strcmp(n, "rror")) {
            *t = json_litex_opts_unexpect_lit_error;
            return true;
        }
        return false;
    case 'i':
        if (!strcmp(n, "gnore")) {
            *t = json_litex_opts_unexpect_lit_ignore;
            return true;
        }
    }
    return false;
}

static bool json_litex_opts_parse_unexpect_lit_optarg(
    const char* opt_name, char* opt_arg,
    struct json_litex_opts_error_t* err,
    enum json_litex_opts_unexpect_lit_t* lit)
{
    // stev: the short option means 'ignore'
    if (opt_arg == NULL) {
        *lit = json_litex_opts_unexpect_lit_ignore;
        return true;
    }

    if (!json_litex_opts_lookup_unexpect_lit_name(
            opt_arg, lit))
        return invalid_opt_arg(
            opt_name,
            opt_arg);

    return true;
}

// $ print 'expanded =json_litex_opts_trie_path_expanded' 'function =json_litex_opts_trie_path_function'|gen-func -f json_litex_opts_lookup_trie_path_name -Pf -q \!strcmp|ssed -R '1s/^/static /;1s/(?<=\()/\n\t/;1s/\bresult_t\&/enum json_litex_opts_trie_path_t*/;s/(?=t =)/*/;s/result_t:://'

static bool json_litex_opts_lookup_trie_path_name(
    const char* n, enum json_litex_opts_trie_path_t* t)
{
    // pattern: expanded|function
    switch (*n ++) {
    case 'e':
        if (!strcmp(n, "xpanded")) {
            *t = json_litex_opts_trie_path_expanded;
            return true;
        }
        return false;
    case 'f':
        if (!strcmp(n, "unction")) {
            *t = json_litex_opts_trie_path_function;
            return true;
        }
    }
    return false;
}

static bool json_litex_opts_parse_trie_path_optarg(
    const char* opt_name, char* opt_arg,
    struct json_litex_opts_error_t* err,
    enum json_litex_opts_trie_path_t* path)
{
    if (!json_litex_opts_lookup_trie_path_name(
            opt_arg, path))
        return invalid_opt_arg(
            opt_name,
            opt_arg);

    return true;
}

// $ print 'builtin =json_litex_opts_path_eq_builtin' 'strcmp =json_litex_opts_path_eq_strcmp'|gen-func -f json_litex_opts_lookup_path_eq_name -Pf -q \!strcmp|ssed -R '1s/^/static /;1s/(?<=\()/\n\t/;1s/\bresult_t\&/enum json_litex_opts_path_eq_t*/;s/(?=t =)/*/;s/result_t:://'

static bool json_litex_opts_lookup_path_eq_name(
    const char* n, enum json_litex_opts_path_eq_t* t)
{
    // pattern: builtin|strcmp
    switch (*n ++) {
    case 'b':
        if (!strcmp(n, "uiltin")) {
            *t = json_litex_opts_path_eq_builtin;
            return true;
        }
        return false;
    case 's':
        if (!strcmp(n, "trcmp")) {
            *t = json_litex_opts_path_eq_strcmp;
            return true;
        }
    }
    return false;
}

static bool json_litex_opts_parse_path_eq_optarg(
    const char* opt_name, char* opt_arg,
    struct json_litex_opts_error_t* err,
    enum json_litex_opts_path_eq_t* eq)
{
    if (!json_litex_opts_lookup_path_eq_name(
            opt_arg, eq))
        return invalid_opt_arg(
            opt_name,
            opt_arg);

    return true;
}

#ifdef JSON_DEBUG
static bool json_litex_opts_parse_debug_optarg(
    const char* opt_name, const char* opt_arg,
    struct json_litex_opts_error_t* err,
    size_t* result)
{
    if (opt_arg == NULL)
        *result = 1;
    else {
        if (!ISDIGIT(opt_arg[0]) || opt_arg[1] != '\0')
            return invalid_opt_arg(opt_name, opt_arg);
        *result = *opt_arg - '0';
    }

    return true;
}
#endif

// stev: comment out the following define to disable
// processing of long command line options using the
// generated function 'json_litex_opts lookup_long_opt'
#define CONFIG_GETOPT_LONG2

#ifdef CONFIG_GETOPT_LONG2

static inline bool prefix(
    const char* p, const char* q)
{
    while (*p && *p == *q)
         ++ p, ++ q;
    return *p == 0;
}

// # long-opts [--debug] [-n]
// $ long-opts() { local d=''; [ "$1" == "--debug" ] && { d='-DDEBUG'; shift; }; ${GCC:-gcc} -Wall -Wextra -std=${GCC_STD:-gnu99} -g -I.. -I. -DPROGRAM=json-litex.so -DJSON_NO_LIB_MAIN -DCONFIG_GEN_LOOKUP_LONG_OPT $d -E json-litex.c -o -|ssed -nR '/\slongs\s*\[\]\s*=\s*\{\s*$/,/^\s+\}\s*;\s*$/{:0;/^\s*(\{\s*".*?"\s*,\s*\d+\s*,\s*(?:\d+|\&\s*[a-z0-9_]+)\s*,\s*[a-z0-9_]+\s*\}\s*,)/!b;s//\1\n/;s/" "//;P;s/^.*?\n//;b0}'|if [ "$1" == '-n' ]; then ssed -nR 's/^.*?"(.*?)".*?$/\1/p'; else cat; fi; }

// $ long-opts|less
// $ long-opts -n|less
// $ long-opts --debug|less
// $ long-opts --debug -n|less

// # gen-long-opts-func0 [--debug]
// $ gen-long-opts-func0() { local d=''; [ "$1" == "--debug" ] && d="$1"; long-opts $d -n|gen-func -r- -f json_litex_opts_lookup_long_opt -e-1 -Pf -uz|ssed -R '1s/^/static /;1s/(?<=\()/\n\t/'; }

// $ gen-long-opts-func() { diff -DJSON_DEBUG <(gen-long-opts-func0) <(gen-long-opts-func0 --debug)|ssed -R 's/(?<=^#else|^#endif)\s+\/\*\s*JSON_DEBUG\s*\*\/\s*$//'; }

// $ gen-long-opts-func

static int json_litex_opts_lookup_long_opt(
    const char* p)
{
    switch (*p ++) {
    case 'c':
        if (prefix(p, "ompiler"))
            return 8;
        return -1;
    case 'd':
#ifndef JSON_DEBUG
        if (prefix(p, "ump-options"))
            return 12;
#else
        switch (*p ++) {
        case 'e':
            if (prefix("bug-", p)) {
                p += 4;
                switch (*p ++) {
                case 'e':
                    if (prefix(p, "xpr"))
                        return 37;
                    return -1;
                case 'l':
                    if (prefix(p, "ib"))
                        return 38;
                    return -1;
                case 'o':
                    if (prefix(p, "bj"))
                        return 39;
                }
            }
            return -1;
        case 'u':
            if (prefix(p, "mp-options"))
                return 12;
        }
#endif
        return -1;
    case 'e':
        if (prefix(p, "xpr-compiler"))
            return 7;
        return -1;
    case 'g':
        if (prefix(p, "en-def"))
            return 3;
        return -1;
    case 'h':
        if (prefix("elp", p)) {
            p += 3;
            if (*p == 0)
#ifndef JSON_DEBUG
                return 38;
#else
                return 42;
#endif
            if (prefix(p, "-sizes"))
#ifndef JSON_DEBUG
                return 37;
#else
                return 41;
#endif
        }
#ifdef JSON_DEBUG
        return -1;
    case 'n':
        if (prefix(p, "o-debug"))
            return 40;
#endif
        return -1;
    case 'p':
        switch (*p ++) {
        case 'a':
            if (prefix("th-", p)) {
                p += 3;
                switch (*p ++) {
                case 'd':
                    if (prefix(p, "ef"))
                        return 4;
                    return -1;
                case 'e':
                    if (prefix(p, "quality"))
                        return 11;
                    return -1;
                case 'l':
                    if (prefix(p, "ib"))
                        return 5;
                    return -1;
                case 'n':
                    if (prefix(p, "ame"))
                        return 6;
                }
            }
            return -1;
        case 'r':
            if (prefix("int-", p)) {
                p += 4;
                switch (*p ++) {
                case 'a':
                    if (prefix(p, "ttr"))
                        return 2;
                    return -1;
                case 's':
                    if (prefix(p, "truct"))
                        return 1;
                }
            }
        }
        return -1;
    case 's':
        if (prefix("izes-", p)) {
            p += 5;
            switch (*p ++) {
            case 'l':
                if (prefix("ib-", p)) {
                    p += 3;
                    switch (*p ++) {
                    case 'b':
                        if (prefix("uf-", p)) {
                            p += 3;
                            switch (*p ++) {
                            case 'i':
                                if (prefix(p, "nit"))
                                    return 24;
                                return -1;
                            case 'm':
                                if (prefix(p, "ax"))
                                    return 23;
                            }
                        }
                        return -1;
                    case 'e':
                        if (prefix("xpr-", p)) {
                            p += 4;
                            switch (*p ++) {
                            case 'a':
                                if (prefix("st-", p)) {
                                    p += 3;
                                    switch (*p ++) {
                                    case 'p':
                                        if (prefix(p, "ool-size"))
                                            return 16;
                                        return -1;
                                    case 's':
                                        if (prefix("tack-", p)) {
                                            p += 5;
                                            switch (*p ++) {
                                            case 'i':
                                                if (prefix(p, "nit"))
                                                    return 18;
                                                return -1;
                                            case 'm':
                                                if (prefix(p, "ax"))
                                                    return 17;
                                            }
                                        }
                                    }
                                }
                                return -1;
                            case 'f':
                                if (prefix("lat-stack-", p)) {
                                    p += 10;
                                    switch (*p ++) {
                                    case 'i':
                                        if (prefix(p, "nit"))
                                            return 15;
                                        return -1;
                                    case 'm':
                                        if (prefix(p, "ax"))
                                            return 14;
                                    }
                                }
                                return -1;
                            case 'n':
                                if (prefix("odes-", p)) {
                                    p += 5;
                                    switch (*p ++) {
                                    case 'i':
                                        if (prefix(p, "nit"))
                                            return 20;
                                        return -1;
                                    case 'm':
                                        if (prefix(p, "ax"))
                                            return 19;
                                    }
                                }
                                return -1;
                            case 'r':
                                if (prefix("exes-", p)) {
                                    p += 5;
                                    switch (*p ++) {
                                    case 'i':
                                        if (prefix(p, "nit"))
                                            return 22;
                                        return -1;
                                    case 'm':
                                        if (prefix(p, "ax"))
                                            return 21;
                                    }
                                }
                            }
                        }
                        return -1;
                    case 'o':
                        if (prefix(p, "wn-pool-size"))
                            return 28;
                        return -1;
                    case 'p':
                        switch (*p ++) {
                        case 'o':
                            if (prefix(p, "ol-size"))
                                return 27;
                            return -1;
                        case 't':
                            if (prefix(p, "r-space-size"))
                                return 29;
                        }
                        return -1;
                    case 's':
                        if (prefix("tack-", p)) {
                            p += 5;
                            switch (*p ++) {
                            case 'i':
                                if (prefix(p, "nit"))
                                    return 26;
                                return -1;
                            case 'm':
                                if (prefix(p, "ax"))
                                    return 25;
                            }
                        }
                        return -1;
                    case 't':
                        if (prefix(p, "ext-max-size"))
                            return 30;
                    }
                }
                return -1;
            case 'o':
                if (prefix("bj-expr-", p)) {
                    p += 8;
                    switch (*p ++) {
                    case 'd':
                        if (prefix(p, "epth-limit"))
                            return 33;
                        return -1;
                    case 'h':
                        if (prefix(p, "eap-limit"))
                            return 34;
                        return -1;
                    case 'm':
                        if (prefix(p, "atch-limit"))
                            return 32;
                        return -1;
                    case 'o':
                        if (prefix(p, "vector-max"))
                            return 31;
                        return -1;
                    case 's':
                        if (prefix("tack-", p)) {
                            p += 5;
                            switch (*p ++) {
                            case 'i':
                                if (prefix(p, "nit"))
                                    return 36;
                                return -1;
                            case 'm':
                                if (prefix(p, "ax"))
                                    return 35;
                            }
                        }
                    }
                }
            }
        }
        return -1;
    case 't':
        if (prefix(p, "rie-path"))
            return 10;
        return -1;
    case 'u':
        if (prefix(p, "nexpect-literals"))
            return 9;
        return -1;
    case 'v':
        switch (*p ++) {
        case 'a':
            if (prefix(p, "lidate"))
                return 0;
            return -1;
        case 'e':
            if (prefix(p, "rsion"))
                return 13;
        }
    }
    return -1;
}

#define GETOPT2_NAME json_litex_opts
#include "lib/getopt2-impl.h"

#endif // CONFIG_GETOPT_LONG2

static bool json_litex_opts_parse(
    struct json_litex_opts_t* opts,
    const struct json_filter_options_t* input,
    struct json_litex_opts_error_t* err)
{
    enum {
        validate_act       = 'V',
        print_struct_act   = 'P',
        print_attr_act     = 'A',
        //!!!CHECK_ATTR check_attr_act = 'C',
        gen_def_act        = 'D',
        path_def_opt       = 'd',
        path_lib_opt       = 'p',
        path_name_opt      = 'n',
        expr_compiler_opt  = 'c',
        unexpect_lit_opt   = 'l',
        trie_path_opt      = 't',
        path_eq_opt        = 'q',
        help_opt           = '?',
        version_opt        = 'v',
        dump_opt           = 128,
#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def) \
        sizes_ ## id ## _opt,
#include "json-litex-sizes.def"
#ifdef JSON_DEBUG
        no_debug_opt,

        debug_expr_opt,
        debug_lib_opt,
        debug_obj_opt,
#endif
        help_sizes_opt,
    };

    // stev: note that function 'json_litex_opts_lookup_long_opt'
    // above needs to be regenerated each and every time 'longs'
    // below changes, when CONFIG_GETOPT_LONG2 has been defined!
    static struct option longs[] = {
        { "validate",           0,       0, validate_act },
        { "print-struct",       0,       0, print_struct_act },
        { "print-attr",         0,       0, print_attr_act },
        //!!!CHECK_ATTR { "check-attr", 0, 0, check_attr_act },
        { "gen-def",            0,       0, gen_def_act },
        { "path-def",           1,       0, path_def_opt },
        { "path-lib",           1,       0, path_lib_opt },
        { "path-name",          2,       0, path_name_opt },
        { "expr-compiler",      1,       0, expr_compiler_opt },
        { "compiler",           1,       0, expr_compiler_opt },
        { "unexpect-literals",  1,       0, unexpect_lit_opt },
        { "trie-path",          1,       0, trie_path_opt },
        { "path-equality",      1,       0, path_eq_opt },
        { "dump-options",       0,       0, dump_opt },
        { "version",            0,       0, version_opt },
#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def) \
        { "sizes-" name,        1,       0, sizes_ ## id ## _opt },
#include "json-litex-sizes.def"
#ifdef JSON_DEBUG
        { "debug-expr",         2,       0, debug_expr_opt },
        { "debug-lib",          2,       0, debug_lib_opt },
        { "debug-obj",          2,       0, debug_obj_opt },
        { "no-debug",           0,       0, no_debug_opt },
#endif
        { "help-sizes",         0,       0, help_sizes_opt },
        { "help",               0, &optopt, help_opt },
        { 0,                    0,       0, 0 }
    };
    static const char shorts[] = ":Ac:Dd:ln::Pp:q:t:Vv";
    int opt;

    ASSERT(input->argc > 0);

    memset(opts, 0, sizeof(*opts));
    opts->argc = input->argc;
    opts->argv = input->argv;

    // stev: assign non-zero defaults:
    opts->path_type = json_litex_opts_path_type_lib;
    opts->trie_path = json_litex_opts_trie_path_function;

#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def) \
    opts->sizes_ ## id = def;
#include "json-litex-sizes.def"

#define argv_optind()                   \
    ({                                  \
        size_t i = INT_AS_SIZE(optind); \
        ASSERT_SIZE_DEC_NO_OVERFLOW(i); \
        ASSERT(i - 1 < opts->argc);     \
        opts->argv[i - 1];              \
    })

#define optopt_char()                   \
    ({                                  \
        ASSERT(ISASCII((char) optopt)); \
        (char) optopt;                  \
    })

    opterr = 0;
    optind = 1;
    while ((opt = getopt_long(
            SIZE_AS_INT(opts->argc), opts->argv,
            shorts, longs, 0)) != EOF) {
        switch (opt) {
        case validate_act:
            opts->action = json_litex_opts_action_validate;
            break;
        case print_struct_act:
            opts->action = json_litex_opts_action_print_struct;
            break;
        case print_attr_act:
            opts->action = json_litex_opts_action_print_attr;
            break;
        //!!!CHECK_ATTR
        //case check_attr_act:
        //	opts->action = json_litex_opts_action_check_attr;
        //	break;
        case gen_def_act:
            opts->action = json_litex_opts_action_gen_def;
            break;
        case path_def_opt:
            opts->path_type = json_litex_opts_path_type_def;
            opts->path_value = *optarg ? optarg : NULL;
            break;
        case path_lib_opt:
            opts->path_type = json_litex_opts_path_type_lib;
            if (!json_litex_opts_parse_path_lib_optarg(
                    "path-lib", optarg, err,
                    &opts->path_value,
                    &opts->path_name))
                return false;
            break;
        case path_name_opt:
            opts->path_name = optarg && *optarg ? optarg : NULL;
            break;
        case expr_compiler_opt:
            if (!json_litex_opts_parse_expr_compiler_optarg(
                    "expr-compiler", optarg, err,
                    &opts->expr_compiler))
                return false;
            break;
        case unexpect_lit_opt:
            if (!json_litex_opts_parse_unexpect_lit_optarg(
                    "unexpect-literals", optarg, err,
                    &opts->unexpect_lit))
                return false;
            break;
        case trie_path_opt:
            if (!json_litex_opts_parse_trie_path_optarg(
                    "trie-path", optarg, err,
                    &opts->trie_path))
                return false;
            break;
        case path_eq_opt:
            if (!json_litex_opts_parse_path_eq_optarg(
                    "path-equality", optarg, err,
                    &opts->path_eq))
                return false;
            break;
        case dump_opt:
            opts->dump = true;
            break;
        case version_opt:
            opts->version = true;
            break;

#undef  CASE
#define CASE(type, id, name, pad, desc, min, max, def)        \
        case sizes_ ## id ## _opt:                            \
            if (!PARSE_SIZE_OPTARG(id, name, type, min, max)) \
                return false;                                 \
            break;
#include "json-litex-sizes.def"

#ifdef JSON_DEBUG
#define CASE_DEBUG_(t, n)                                \
        case debug_ ## t ## _opt: {                      \
                size_t __n;                              \
                if (!json_litex_opts_parse_debug_optarg( \
                        n, optarg, err, &__n))           \
                    return false;                        \
                ASSERT(SIZE_IS_BITS(__n, debug_bits));   \
                opts->debug_ ## t = __n;                 \
            break;                                       \
        }
#define CASE_DEBUG(t) CASE_DEBUG_(t, "debug-" #t)

        CASE_DEBUG(expr)
        CASE_DEBUG(lib)
        CASE_DEBUG(obj)

        case no_debug_opt:
            opts->debug_expr = 0;
            opts->debug_lib = 0;
            opts->debug_obj = 0;
            break;
#endif

        case help_sizes_opt:
            opts->usage_sizes = true;
            break;
        case 0:
            opts->usage = true;
            break;
        case ':': {
            const char* opt = argv_optind();
            if (opt[0] == '-' && opt[1] == '-')
                return missing_opt_arg_str(opt);
            else
                return missing_opt_arg_ch(optopt_char());
        }
        case '?':
        default:
            if (optopt == 0)
                return invalid_opt_str(argv_optind());
            else
            if (optopt != '?') {
                char* opt = argv_optind();
                if (opt[0] == '-' && opt[1] == '-') {
                    char* end = strchr(opt, '=');
                    if (end) *end = '\0';
                    return not_allowed_opt_arg(opt);
                }
                else
                    return invalid_opt_ch(optopt_char());
            }
            else
                opts->usage = true;
            break;
        }
    }

#undef optopt_char
#undef argv_optind

    ASSERT(INT_AS_SIZE(optind) <= opts->argc);

    opts->argc -= optind;
    opts->argv += optind;

    if (opts->argc > 0) {
        opts->path_type = json_litex_opts_path_type_lib;
        opts->path_value = *opts->argv ++;
        opts->argc --;
    }

    if (opts->path_type == json_litex_opts_path_type_lib &&
        opts->path_value && !strcmp(opts->path_value, "-"))
        opts->path_value = NULL;

#ifdef JSON_DEBUG
    json_litex_debug_set_level(
        json_litex_debug_expr_class, opts->debug_expr);
    json_litex_debug_set_level(
        json_litex_debug_lib_class, opts->debug_lib);
    json_litex_debug_set_level(
        json_litex_debug_obj_class, opts->debug_obj);
#endif

    return true;
}

#undef invalid_opt_ch
#undef invalid_opt_str
#undef not_allowed_opt_arg
#undef missing_opt_arg_ch
#undef missing_opt_arg_str
#undef illegal_opt_arg
#undef invalid_opt_arg

#undef arg_error
#undef opt_error

#undef  CASE
#define CASE(t, n, m, ...)                       \
    case json_litex_opts_error_type_ ## n:       \
        STATIC(TYPEOF_IS(error->n, struct        \
            json_litex_opts_error_ ## t ## _t)); \
        fprintf(file, m, ## __VA_ARGS__);        \
        break;

#define CASE_OPT_ARG(n, m) \
        CASE(opt_arg, n, m, error->n.name, error->n.arg)
#define CASE_OPT_STR(n, m) \
        CASE(opt_str, n, m, error->n.name)
#define CASE_OPT_CH(n, m) \
        CASE(opt_ch, n, m, error->n.name)

static void json_litex_opts_error_print_desc(
    const struct json_litex_opts_error_t* error,
    FILE* file)
{
    switch (error->type) {

    CASE_OPT_ARG(invalid_opt_arg,
        "invalid argument for '%s' option: '%s'");
    CASE_OPT_ARG(illegal_opt_arg,
        "illegal argument for '%s' option: '%s'");
    CASE_OPT_STR(missing_opt_arg_str,
        "argument for option '%s' not found");
    CASE_OPT_CH(missing_opt_arg_ch,
        "argument for option '-%c' not found");
    CASE_OPT_STR(not_allowed_opt_arg,
        "option '%s' does not allow an argument");
    CASE_OPT_STR(invalid_opt_str,
        "invalid command line option '%s'");
    CASE_OPT_CH(invalid_opt_ch,
        "invalid command line option '-%c'");

    default:
        UNEXPECT_VAR("%d", error->type);
    }
}

#undef CASE_OPT_CH
#undef CASE_OPT_STR
#undef CASE_OPT_ARG
#undef CASE

static struct json_litex_lib_spec_t
    json_litex_opts_make_spec(
        const struct json_litex_opts_t* opts)
{
    struct json_litex_lib_spec_t r;

    switch (opts->path_type) {

    case json_litex_opts_path_type_def:
        r.type = json_litex_lib_text_spec_type;
        r.text.def = PTR_UCHAR_CAST_CONST(
            opts->path_value);
        r.path_name = opts->path_name;
        break;

    case json_litex_opts_path_type_lib:
        r.type = json_litex_lib_file_spec_type;
        r.file.name = opts->path_value;
        r.path_name = opts->path_name;
        break;

    default:
        UNEXPECT_VAR("%d", opts->path_type);
    }

    return r;
}

enum json_litex_filter_impl_type_t
{
    json_litex_filter_impl_type_obj,
    json_litex_filter_impl_type_lib,
};

enum json_litex_filter_error_type_t
{
    json_litex_filter_error_type_none,
    json_litex_filter_error_type_opts,
    json_litex_filter_error_type_stdin,
    json_litex_filter_error_type_action,
    json_litex_filter_error_type_info,
    json_litex_filter_error_type_mode,
    json_litex_filter_error_type_impl,
};

struct json_litex_filter_error_t
{
    enum json_litex_filter_error_type_t    type;
    union {
        struct json_litex_opts_error_t     opts;
        enum json_litex_filter_impl_type_t mode;
    };
};

#define JSON_LITEX_FILTER_ERROR_IS(n)      \
    (                                      \
        filter->error.type ==              \
        json_litex_filter_error_type_ ## n \
    )

#define JSON_LITEX_FILTER_ERROR(t, r)           \
    ({                                          \
        filter->error.type =                    \
            json_litex_filter_error_type_ ## t; \
        r;                                      \
    })
#define JSON_LITEX_FILTER_ERROR_ARG(t, v, r)    \
    ({                                          \
        filter->error.type =                    \
            json_litex_filter_error_type_ ## t; \
        filter->error.t = v;                    \
        r;                                      \
    })
#define JSON_LITEX_FILTER_ERROR_OPTS(v, r) \
        JSON_LITEX_FILTER_ERROR_ARG(opts, v, r)
#define JSON_LITEX_FILTER_ERROR_MODE(r) \
        JSON_LITEX_FILTER_ERROR_ARG(mode, filter->impl.type, r)

#define json_litex_filter_obj_impl_t json_litex_obj_t
#define json_litex_filter_lib_impl_t json_litex_lib_t

typedef void
    (*json_litex_filter_impl_done_func_t)(void*);
typedef bool
    (*json_litex_filter_impl_get_is_error_func_t)(void*);
typedef struct json_error_pos_t
    (*json_litex_filter_impl_get_error_pos_func_t)(void*);
typedef struct json_file_info_t*
    (*json_litex_filter_impl_get_error_file_func_t)(void*);
typedef void
    (*json_litex_filter_impl_print_error_desc_func_t)(void*, FILE*);

struct json_litex_filter_impl_t
{
    enum json_litex_filter_impl_type_t type;
    union {
        struct json_litex_filter_obj_impl_t obj;
        struct json_litex_filter_lib_impl_t lib;
    };

    json_litex_filter_impl_done_func_t             done;
    json_litex_filter_impl_get_is_error_func_t     get_is_error;
    json_litex_filter_impl_get_error_pos_func_t    get_error_pos;
    json_litex_filter_impl_get_error_file_func_t   get_error_file;
    json_litex_filter_impl_print_error_desc_func_t print_error_desc;
};

struct json_litex_filter_t
{
    struct json_litex_opts_t         opts;
    struct json_litex_filter_impl_t  impl;
    struct json_litex_filter_error_t error;
};

#define JSON_LITEX_FILTER_IMPL_IS(n) \
    (filter->impl.type == json_litex_filter_impl_type_ ## n)
#define JSON_LITEX_FILTER_IMPL_AS(n)          \
    ({                                        \
        ASSERT(JSON_LITEX_FILTER_IMPL_IS(n)); \
        &(filter->impl.n);                    \
    })
#define JSON_LITEX_FILTER_IMPL_AS_IF(n) \
    (                                   \
        JSON_LITEX_FILTER_IMPL_IS(n)    \
        ? &(filter->impl.n) : NULL      \
    )

#define JSON_LITEX_FILTER_IMPL_PTR()                      \
    ({                                                    \
        void* __r;                                        \
        if (!(__r = JSON_LITEX_FILTER_IMPL_AS_IF(obj)) && \
            !(__r = JSON_LITEX_FILTER_IMPL_AS_IF(lib)))   \
            UNEXPECT_VAR("%d", filter->impl.type);        \
        __r;                                              \
    })

#define JSON_LITEX_FILTER_CALL_HANDLER(n, ...)       \
    ({                                               \
        struct json_litex_filter_obj_impl_t* __o =   \
          JSON_LITEX_FILTER_IMPL_AS_IF(obj);         \
          __o == NULL                                \
        ? JSON_LITEX_FILTER_ERROR_MODE(false)        \
        : json_litex_obj_ ## n(__o, ## __VA_ARGS__); \
    })

static bool json_litex_filter_null(struct json_litex_filter_t* filter)
{ return JSON_LITEX_FILTER_CALL_HANDLER(null); }

static bool json_litex_filter_boolean(struct json_litex_filter_t* filter,
    bool val)
{ return JSON_LITEX_FILTER_CALL_HANDLER(boolean, val); }

static bool json_litex_filter_number(struct json_litex_filter_t* filter,
    const uchar_t* val, size_t len)
{ return JSON_LITEX_FILTER_CALL_HANDLER(number, val, len); }

static bool json_litex_filter_string(struct json_litex_filter_t* filter,
    const uchar_t* val, size_t len)
{ return JSON_LITEX_FILTER_CALL_HANDLER(string, val, len); }

static bool json_litex_filter_object_start(struct json_litex_filter_t* filter)
{ return JSON_LITEX_FILTER_CALL_HANDLER(object_start); }

static bool json_litex_filter_object_key(struct json_litex_filter_t* filter,
    const uchar_t* key, size_t len)
{ return JSON_LITEX_FILTER_CALL_HANDLER(object_key, key, len); }

static bool json_litex_filter_object_sep(struct json_litex_filter_t* filter)
{ return JSON_LITEX_FILTER_CALL_HANDLER(object_sep); }

static bool json_litex_filter_object_end(struct json_litex_filter_t* filter)
{ return JSON_LITEX_FILTER_CALL_HANDLER(object_end); }

static bool json_litex_filter_array_start(struct json_litex_filter_t* filter)
{ return JSON_LITEX_FILTER_CALL_HANDLER(array_start); }

static bool json_litex_filter_array_sep(struct json_litex_filter_t* filter)
{ return JSON_LITEX_FILTER_CALL_HANDLER(array_sep); }

static bool json_litex_filter_array_end(struct json_litex_filter_t* filter)
{ return JSON_LITEX_FILTER_CALL_HANDLER(array_end); }

static bool json_litex_filter_value_sep(struct json_litex_filter_t* filter)
{ return JSON_LITEX_FILTER_CALL_HANDLER(value_sep); }

static const struct json_handler_t json_litex_filter_handler = {
    .null_func         = (json_null_func_t)         json_litex_filter_null,
    .boolean_func      = (json_boolean_func_t)      json_litex_filter_boolean,
    .number_func       = (json_number_func_t)       json_litex_filter_number,
    .string_func       = (json_string_func_t)       json_litex_filter_string,
    .object_start_func = (json_object_start_func_t) json_litex_filter_object_start,
    .object_key_func   = (json_object_key_func_t)   json_litex_filter_object_key,
    .object_sep_func   = (json_object_sep_func_t)   json_litex_filter_object_sep,
    .object_end_func   = (json_object_end_func_t)   json_litex_filter_object_end,
    .array_start_func  = (json_array_start_func_t)  json_litex_filter_array_start,
    .array_sep_func    = (json_array_sep_func_t)    json_litex_filter_array_sep,
    .array_end_func    = (json_array_end_func_t)    json_litex_filter_array_end,
    .value_sep_func    = (json_value_sep_func_t)    json_litex_filter_value_sep,
};

size_t json_litex_filter_get_version(void)
{ return JSON_FILTER_VERSION; }

static struct json_litex_filter_t* json_litex_filter_create_base(
    enum json_litex_filter_impl_type_t type,
    const struct json_filter_options_t* opts)
{
    const size_t n = sizeof(struct json_litex_filter_t);
    struct json_litex_filter_t* filter;
    struct json_litex_opts_error_t e;

    filter = malloc(n);
    VERIFY(filter != NULL);
    memset(filter, 0, n);

    if (type == json_litex_filter_impl_type_obj ||
        type == json_litex_filter_impl_type_lib)
        filter->impl.type = type;
    else
        UNEXPECT_VAR("%d", type);

    if (!json_litex_opts_parse(&filter->opts, opts, &e))
        return JSON_LITEX_FILTER_ERROR_OPTS(e, filter);

    if (JSON_LITEX_FILTER_IMPL_IS(lib) &&
        filter->opts.action == json_litex_opts_action_none)
        filter->opts.action = json_litex_opts_action_validate;

    if (JSON_LITEX_FILTER_IMPL_IS(obj) &&
        filter->opts.action != json_litex_opts_action_none)
        return JSON_LITEX_FILTER_ERROR(action, filter);

    if (JSON_LITEX_FILTER_IMPL_IS(obj) &&
        json_litex_opts_is_info(&filter->opts))
        return JSON_LITEX_FILTER_ERROR(info, filter);

    if (JSON_LITEX_FILTER_IMPL_IS(obj) &&
        filter->opts.path_value == NULL &&
        filter->opts.path_type == json_litex_opts_path_type_lib)
        return JSON_LITEX_FILTER_ERROR(stdin, filter);

    return filter;
}

static void json_litex_filter_obj_impl_init(
    struct json_litex_filter_obj_impl_t* impl,
    const struct json_handler_obj_t* hobj,
    const struct json_handler_t** handler,
    const struct json_litex_lib_spec_t* spec,
    const struct json_litex_obj_sizes_t* sizes,
    const struct json_litex_lib_opts_t* lib_opts,
    const struct json_litex_obj_opts_t* opts)
{
    json_litex_obj_init(impl, hobj, spec, sizes, lib_opts, opts);
    *handler = &json_litex_filter_handler;
}

#define json_litex_filter_lib_impl_init json_litex_lib_init_from_spec

#define JSON_LITEX_EXPR_FLAT_SIZES(n)                             \
    {                                                             \
        .stack_max = filter->opts.sizes_lib_expr_flat_stack_max,  \
        .stack_init = filter->opts.sizes_lib_expr_flat_stack_init \
    }
#define JSON_LITEX_EXPR_AST_SIZES(n)                             \
    {                                                            \
        .pool_size = filter->opts.sizes_lib_expr_ast_pool_size,  \
        .stack_max = filter->opts.sizes_lib_expr_ast_stack_max,  \
        .stack_init = filter->opts.sizes_lib_expr_ast_stack_init \
    }
#define JSON_LITEX_EXPR_SIZES(n)                              \
    {                                                         \
        .flat = JSON_LITEX_EXPR_FLAT_SIZES(),                 \
        .ast = JSON_LITEX_EXPR_AST_SIZES(),                   \
        .nodes_max = filter->opts.sizes_lib_expr_nodes_max,   \
        .nodes_init = filter->opts.sizes_lib_expr_nodes_init, \
        .rexes_max = filter->opts.sizes_lib_expr_rexes_max,   \
        .rexes_init = filter->opts.sizes_lib_expr_rexes_init  \
    }
#define JSON_LITEX_EXPR_VM_SIZES(n)                             \
    {                                                           \
        .ovector_max = filter->opts.sizes_obj_expr_ovector_max, \
        .match_limit = filter->opts.sizes_obj_expr_match_limit, \
        .depth_limit = filter->opts.sizes_obj_expr_depth_limit, \
        .heap_limit = filter->opts.sizes_obj_expr_heap_limit,   \
        .stack_max = filter->opts.sizes_obj_expr_stack_max,     \
        .stack_init = filter->opts.sizes_obj_expr_stack_init    \
    }
#define JSON_LITEX_JSON_OBJ_SIZES(n)                    \
    {                                                   \
        .buf_max = filter->opts.sizes_lib_buf_max,      \
        .buf_init = filter->opts.sizes_lib_buf_init,    \
        .stack_max = filter->opts.sizes_lib_stack_max,  \
        .stack_init = filter->opts.sizes_lib_stack_init \
    }
#define JSON_LITEX_JSON_AST_SIZES(n)                  \
    {                                                 \
        .obj = JSON_LITEX_JSON_OBJ_SIZES(),           \
        .pool_size = filter->opts.sizes_lib_pool_size \
    }
#define JSON_LITEX_LIB_SIZES()                                   \
    {                                                            \
        .expr = JSON_LITEX_EXPR_SIZES(),                         \
        .ast = JSON_LITEX_JSON_AST_SIZES(),                      \
        .pool_size = filter->opts.sizes_lib_own_pool_size,       \
        .ptr_space_size = filter->opts.sizes_lib_ptr_space_size, \
        .text_max_size = filter->opts.sizes_lib_text_max_size    \
    }
#define JSON_LITEX_OBJ_SIZES()              \
    {                                       \
        .expr = JSON_LITEX_EXPR_VM_SIZES(), \
        .lib = JSON_LITEX_LIB_SIZES()       \
    }

#define JSON_LITEX_FILTER_IMPL_FUNC_INIT(f, n)                 \
    filter->impl.f = (json_litex_filter_impl_ ## f ## _func_t) \
        json_litex_ ## n ## _ ## f

#define JSON_LITEX_FILTER_INIT_IMPL(n, ...)                    \
    do {                                                       \
        JSON_LITEX_FILTER_IMPL_FUNC_INIT(done, n);             \
        JSON_LITEX_FILTER_IMPL_FUNC_INIT(get_is_error, n);     \
        JSON_LITEX_FILTER_IMPL_FUNC_INIT(get_error_pos, n);    \
        JSON_LITEX_FILTER_IMPL_FUNC_INIT(get_error_file, n);   \
        JSON_LITEX_FILTER_IMPL_FUNC_INIT(print_error_desc, n); \
        json_litex_filter_ ## n ## _impl_init(                 \
            JSON_LITEX_FILTER_IMPL_AS(n),                      \
            ## __VA_ARGS__);                                   \
    } while (0)

struct json_litex_filter_t* json_litex_filter_create_filter(
    const struct json_filter_options_t* opts,
    const struct json_handler_obj_t* hobj,
    const struct json_handler_t** handler)
{
    struct json_litex_filter_t* filter;
    struct json_litex_obj_sizes_t s;
    struct json_litex_lib_spec_t p;
    struct json_litex_lib_opts_t l;
    struct json_litex_obj_opts_t o;

    ASSERT(hobj != NULL);
    ASSERT(opts != NULL);
    ASSERT(handler != NULL);

    filter = json_litex_filter_create_base(
        json_litex_filter_impl_type_obj,
        opts);
    if (!JSON_LITEX_FILTER_ERROR_IS(none))
        return filter;

    memset(&l, 0, sizeof(l));
    memset(&o, 0, sizeof(o));

    // stev: when in 'filter' mode, expressions get
    // compiled by default with the 'flat' compiler!
    if (filter->opts.expr_compiler !=
            json_litex_opts_expr_compiler_ast)
        l.gen_attr |= json_litex_gen_attr_opts_flat_expr_impl;

    if (filter->opts.unexpect_lit ==
            json_litex_opts_unexpect_lit_ignore)
        o.path |= json_litex_obj_path_opts_ignore_err_lit;

    s = (struct json_litex_obj_sizes_t)
        JSON_LITEX_OBJ_SIZES();
    p = json_litex_opts_make_spec(
        &filter->opts);

    JSON_LITEX_FILTER_INIT_IMPL(
        obj, hobj, handler, &p, &s, &l, &o);

    return filter;
}

struct json_litex_filter_t* json_litex_filter_create_exec(
    const struct json_filter_options_t* opts)
{
    struct json_litex_filter_t* filter;
    struct json_litex_lib_sizes_t s;
    struct json_litex_lib_spec_t p;
    struct json_litex_lib_opts_t o;
    bool b;

    ASSERT(opts != NULL);

    filter = json_litex_filter_create_base(
        json_litex_filter_impl_type_lib,
        opts);
    if (!JSON_LITEX_FILTER_ERROR_IS(none))
        return filter;

    memset(&o, 0, sizeof(o));

    // stev: in 'exec' mode, when printing out
    // generated attributes or when generating
    // litex def code, keep regexes plain texts
    if (filter->opts.action ==
            json_litex_opts_action_gen_def ||
        filter->opts.action ==
            json_litex_opts_action_print_attr)
        o.expr |= json_litex_expr_opts_keep_regex_texts;

    // stev: when in 'exec' mode, expressions get
    // compiled by default with the 'ast' compiler!
    if (filter->opts.expr_compiler ==
            json_litex_opts_expr_compiler_flat)
        o.gen_attr |= json_litex_gen_attr_opts_flat_expr_impl;

    if (filter->opts.trie_path ==
            json_litex_opts_trie_path_expanded)
        o.gen_def |= json_litex_gen_def_opts_expanded;
    if (filter->opts.path_eq ==
            json_litex_opts_path_eq_strcmp)
        o.gen_def |= json_litex_gen_def_opts_strcmp;

    b = json_litex_opts_is_info(&filter->opts);
    s = (struct json_litex_lib_sizes_t)
        JSON_LITEX_LIB_SIZES();
    p = json_litex_opts_make_spec(
        &filter->opts);

    JSON_LITEX_FILTER_INIT_IMPL(
        lib, &p, &s, &o, !b);

    return filter;
}

void json_litex_filter_destroy(
    struct json_litex_filter_t* filter)
{
    if (filter->impl.done != NULL)
        filter->impl.done(
            JSON_LITEX_FILTER_IMPL_PTR());
    free(filter);
}

bool json_litex_filter_get_is_error(
    struct json_litex_filter_t* filter)
{
    if (!JSON_LITEX_FILTER_ERROR_IS(none))
        return true;
    if (filter->impl.get_is_error(
            JSON_LITEX_FILTER_IMPL_PTR()))
        return JSON_LITEX_FILTER_ERROR(impl, true);
    else
        return false;
}

struct json_error_pos_t
    json_litex_filter_get_error_pos(
        struct json_litex_filter_t* filter)
{
    return
        JSON_LITEX_FILTER_ERROR_IS(impl)
        ? filter->impl.get_error_pos(
            JSON_LITEX_FILTER_IMPL_PTR())
        : (struct json_error_pos_t) {0, 0};
}

const struct json_file_info_t*
    json_litex_filter_get_error_file(
        struct json_litex_filter_t* filter)
{
    return
        JSON_LITEX_FILTER_ERROR_IS(impl)
        ? filter->impl.get_error_file(
            JSON_LITEX_FILTER_IMPL_PTR())
        : NULL;
}

#undef  CASE
#define CASE(n, v) \
    case json_litex_filter_impl_type_ ## n: \
        return #v

static const char* json_litex_filter_impl_type_get_name(
    enum json_litex_filter_impl_type_t type)
{
    switch (type) {
    CASE(obj, filter);
    CASE(lib, exec);
    default:
        UNEXPECT_VAR("%d", type);
    }
}

#undef  CASE
#define CASE(n) case json_litex_filter_error_type_ ## n

void json_litex_filter_print_error_desc(
    struct json_litex_filter_t* filter,
    FILE* file)
{
    const struct json_litex_filter_error_t* e =
        &filter->error;
    size_t k = 0;

PRAGMA_DIAG_PUSH_IGNORED_IMPLICIT_FALLTHROUGH

    switch (e->type) {

    CASE(none):
        fputs("no error", file);
        break;
    CASE(opts):
        json_litex_opts_error_print_desc(
            &e->opts, file);
        break;
    CASE(stdin):
        fprintf(file,
            "input cannot be stdin when library mode is '%s'",
            json_litex_filter_impl_type_get_name(e->mode));
        break;
    CASE(action):
        k ++;
    CASE(info):
        fprintf(file,
            "%s options not allowed when library mode is '%s'",
            k ? "action" : "info",
            json_litex_filter_impl_type_get_name(e->mode));
        break;
    CASE(mode):
        fprintf(file, "invalid library mode '%s'",
            json_litex_filter_impl_type_get_name(e->mode));
        break;
    CASE(impl):
        filter->impl.print_error_desc(
            JSON_LITEX_FILTER_IMPL_PTR(), file);
        break;

    default:
        UNEXPECT_VAR("%d", e->type);
    }

PRAGMA_DIAG_POP_IGNORED_IMPLICIT_FALLTHROUGH
}

#undef  CASE
#define CASE(n) \
    [json_litex_opts_action_ ## n] = json_litex_lib_ ## n

bool json_litex_filter_exec(struct json_litex_filter_t* filter)
{
    typedef bool (*act_func_t)(struct json_litex_lib_t*);
    static const act_func_t acts[] = {
        CASE(validate),
        CASE(print_struct),
        CASE(print_attr),
        //!!!CHECK_ATTR CASE(check_attr),
        CASE(gen_def),
    };
    struct json_litex_filter_lib_impl_t* impl;
    act_func_t act;

    if (json_litex_opts_print_info(&filter->opts))
        return true;

    if (!(impl = JSON_LITEX_FILTER_IMPL_AS_IF(lib)))
        return JSON_LITEX_FILTER_ERROR_MODE(false);

    act = ARRAY_NULL_ELEM(acts, filter->opts.action);
    ASSERT(act != NULL);

    return act(impl);
}

#ifdef CONFIG_DYNAMIC_LINKER

JSON_API const char json_litex_interp[]
    __attribute__((section(".interp"))) =
    CONFIG_DYNAMIC_LINKER;

void json_litex_filter_main(void)
    NORETURN;

void json_litex_filter_main(void)
{
    json_litex_version();
    fflush(stdout);

    _exit(0);
}

#endif

#endif // JSON_LITEX_TEST_EXPR


