<commit_before>/*
 * Copyright 2016 GoPro Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <string.h>
#include <inttypes.h>

#include "bstr.h"
#include "log.h"
#include "hmap.h"
#include "memory.h"
#include "nodegl.h"
#include "nodes.h"
#include "params.h"
#include "utils.h"

const struct param_specs ngli_params_specs[] = {
    [PARAM_TYPE_INT] = {
        .name = "int",
        .size = sizeof(int),
        .desc = NGLI_DOCSTRING("Integer"),
    },
    [PARAM_TYPE_BOOL] = {
        .name = "bool",
        .size = sizeof(int),
        .desc = NGLI_DOCSTRING("Boolean (map to `int` in C)"),
    },
    [PARAM_TYPE_I64] = {
        .name = "i64",
        .size = sizeof(int64_t),
        .desc = NGLI_DOCSTRING("64-bit integer"),
    },
    [PARAM_TYPE_DBL] = {
        .name = "double",
        .size = sizeof(double),
        .desc = NGLI_DOCSTRING("Double-precision float"),
    },
    [PARAM_TYPE_STR] = {
        .name = "string",
        .size = sizeof(char *),
        .desc = NGLI_DOCSTRING("String"),
    },
    [PARAM_TYPE_DATA] = {
        .name = "data",
        .size = sizeof(void *) + sizeof(int),
        .desc = NGLI_DOCSTRING("Agnostic data buffer"),
    },
    [PARAM_TYPE_VEC2] = {
        .name = "vec2",
        .size = sizeof(float[2]),
        .desc = NGLI_DOCSTRING("2 single-precision floats"),
    },
    [PARAM_TYPE_VEC3] = {
        .name = "vec3",
        .size = sizeof(float[3]),
        .desc = NGLI_DOCSTRING("3 single-precision floats"),
    },
    [PARAM_TYPE_VEC4] = {
        .name = "vec4",
        .size = sizeof(float[4]),
        .desc = NGLI_DOCSTRING("4 single-precision floats"),
    },
    [PARAM_TYPE_MAT4] = {
        .name = "mat4",
        .size = sizeof(float[4*4]),
        .desc = NGLI_DOCSTRING("4x4 single-precision floats"),
    },
    [PARAM_TYPE_NODE] = {
        .name = "Node",
        .size = sizeof(struct ngl_node *),
        .desc = NGLI_DOCSTRING("node.gl Node"),
    },
    [PARAM_TYPE_NODELIST] = {
        .name = "NodeList",
        .size = sizeof(struct ngl_node **) + sizeof(int),
        .desc = NGLI_DOCSTRING("List of node.gl Node"),
    },
    [PARAM_TYPE_DBLLIST] = {
        .name = "doubleList",
        .size = sizeof(double *) + sizeof(int),
        .desc = NGLI_DOCSTRING("List of double-precision floats"),
    },
    [PARAM_TYPE_NODEDICT] = {
        .name = "NodeDict",
        .size = sizeof(struct hmap *),
        .desc = NGLI_DOCSTRING("Dictionary mapping arbitrary string identifiers to node.gl Nodes"),
    },
    [PARAM_TYPE_SELECT] = {
        .name = "select",
        .size = sizeof(int),
        .desc = NGLI_DOCSTRING("Selection of one constant (expressed as a string)"),
    },
    [PARAM_TYPE_FLAGS] = {
        .name = "flags",
        .size = sizeof(int),
        .desc = NGLI_DOCSTRING("Combination of constants (expressed as strings), using `+` as separator. Can be empty for none."),
    },
    [PARAM_TYPE_RATIONAL] = {
        .name = "rational",
        .size = sizeof(int[2]),
        .desc = NGLI_DOCSTRING("Rational number (expressed as 2 integers, respectively as numerator and denominator)"),
    },
};

const struct node_param *ngli_params_find(const struct node_param *params, const char *key)
{
    if (!params)
        return NULL;
    for (int i = 0; params[i].key; i++) {
        const struct node_param *par = &params[i];
        if (!strcmp(par->key, key))
            return par;
    }
    return NULL;
}

int ngli_params_get_select_val(const struct param_const *consts, const char *s, int *dst)
{
    for (int i = 0; consts[i].key; i++) {
        if (!strcmp(consts[i].key, s)) {
            *dst = consts[i].value;
            return 0;
        }
    }
    return -1;
}

const char *ngli_params_get_select_str(const struct param_const *consts, int val)
{
    for (int i = 0; consts[i].key; i++)
        if (consts[i].value == val)
            return consts[i].key;
    return NULL;
}

/* The first separator (`+`) is used as default separator because `|` can not
 * be used due to markdown table formatting, and ` ` will cause parsing issue
 * in the serialization.
 */
#define FLAGS_SEP "+| "

int ngli_params_get_flags_val(const struct param_const *consts, const char *s, int *dst)
{
    *dst = 0;
    if (!strcmp(s, "0"))
        return 0;
    if (!strcmp(s, "unset")) {
        *dst = -1;
        return 0;
    }
    while (*s) {
        int i;
        const size_t len = strcspn(s, FLAGS_SEP);
        for (i = 0; consts[i].key; i++) {
            if (!strncmp(consts[i].key, s, len)) {
                /* -1 is a reserved value and means that the mask is unset */
                ngli_assert(consts[i].value != -1);
                *dst |= consts[i].value;
                break;
            }
        }
        if (!consts[i].key) {
            LOG(ERROR, "unrecognized \"%.*s\" flag", (int)len, s);
            return -1;
        }
        s += len;
        while (*s && strchr(FLAGS_SEP, *s))
            s++;
    }
    return 0;
}

char *ngli_params_get_flags_str(const struct param_const *consts, int val)
{
    if (!val)
        return ngli_strdup("0");

    if (val == -1)
        return ngli_strdup("unset");

    struct bstr *b = ngli_bstr_create();
    if (!b)
        return NULL;

    for (int i = 0; consts[i].key; i++)
        if (val & consts[i].value)
            ngli_bstr_print(b, "%.1s%s", *ngli_bstr_strptr(b) ? FLAGS_SEP : "", consts[i].key);

    char *ret = ngli_bstr_strdup(b);
    ngli_bstr_freep(&b);
    return ret;
}

void ngli_params_bstr_print_val(struct bstr *b, uint8_t *base_ptr, const struct node_param *par)
{
    switch (par->type) {
        case PARAM_TYPE_DBL: {
            const double v = *(double *)(base_ptr + par->offset);
            ngli_bstr_print(b, "%g", v);
            break;
        }
        case PARAM_TYPE_SELECT: {
            const int v = *(int *)(base_ptr + par->offset);
            const char *s = ngli_params_get_select_str(par->choices->consts, v);
            ngli_assert(s);
            ngli_bstr_print(b, "%s", s);
            break;
        }
        case PARAM_TYPE_FLAGS: {
            const int v = *(int *)(base_ptr + par->offset);
            char *s = ngli_params_get_flags_str(par->choices->consts, v);
            if (!s)
                break;
            ngli_assert(*s);
            ngli_bstr_print(b, "%s", s);
            ngli_free(s);
            break;
        }
        case PARAM_TYPE_BOOL: {
            const int v = *(int *)(base_ptr + par->offset);
            if (v == -1)
                ngli_bstr_print(b, "unset");
            else
                ngli_bstr_print(b, "%d", v);
            break;
        }
        case PARAM_TYPE_INT: {
            const int v = *(int *)(base_ptr + par->offset);
            ngli_bstr_print(b, "%d", v);
            break;
        }
        case PARAM_TYPE_I64: {
            const int64_t v = *(int64_t *)(base_ptr + par->offset);
            ngli_bstr_print(b, "%" PRId64, v);
            break;
        }
        case PARAM_TYPE_VEC2: {
            const float *v = (const float *)(base_ptr + par->offset);
            ngli_bstr_print(b, "(%g,%g)", v[0], v[1]);
            break;
        }
        case PARAM_TYPE_VEC3: {
            const float *v = (const float *)(base_ptr + par->offset);
            ngli_bstr_print(b, "(%g,%g,%g)", v[0], v[1], v[2]);
            break;
        }
        case PARAM_TYPE_VEC4: {
            const float *v = (const float *)(base_ptr + par->offset);
            ngli_bstr_print(b, "(%g,%g,%g,%g)", v[0], v[1], v[2], v[3]);
            break;
        }
        case PARAM_TYPE_MAT4: {
            const float *m = (const float *)(base_ptr + par->offset);
            ngli_bstr_print(b,
                            "(%g,%g,%g,%g %g,%g,%g,%g %g,%g,%g,%g %g,%g,%g,%g)",
                            m[ 0], m[ 1], m[ 2], m[ 3],
                            m[ 4], m[ 5], m[ 6], m[ 7],
                            m[ 8], m[ 9], m[10], m[11],
                            m[12], m[13], m[14], m[15]);
            break;
        }
        case PARAM_TYPE_STR: {
            const char *s = *(const char **)(base_ptr + par->offset);
            if (!s)
                ngli_bstr_print(b, "\"\"");
            else if (strchr(s, '\n')) // print a checksum when the string is multiline (typically, shaders)
                ngli_bstr_print(b, "%08X <i>(CRC32)</i>", ngli_crc32(s));
            else if (strchr(s, '/')) // assume a file and display only basename
                ngli_bstr_print(b, "\"%s\"", strrchr(s, '/') + 1);
            else
                ngli_bstr_print(b, "\"%s\"", s);
            break;
        }
        case PARAM_TYPE_DBLLIST: {
            uint8_t *elems_p = base_ptr + par->offset;
            uint8_t *nb_elems_p = base_ptr + par->offset + sizeof(double *);
            const double *elems = *(double **)elems_p;
            const int nb_elems = *(int *)nb_elems_p;
            for (int i = 0; i < nb_elems; i++)
                ngli_bstr_print(b, "%s%g", i ? "," : "", elems[i]);
            break;
        }
        case PARAM_TYPE_RATIONAL: {
            const int *r = (int *)(base_ptr + par->offset);
            ngli_bstr_print(b, "%d/%d", r[0], r[1]);
            break;
        }
    }
}

static int allowed_node(const struct ngl_node *node, const int *allowed_ids)
{
    if (!allowed_ids)
        return 1;
    const int id = node->class->id;
    for (int i = 0; allowed_ids[i] != -1; i++)
        if (id == allowed_ids[i])
            return 1;
    return 0;
}

static void node_hmap_free(void *user_arg, void *data)
{
    struct ngl_node *node = data;
    ngl_node_unrefp(&node);
}

int ngli_params_set(uint8_t *base_ptr, const struct node_param *par, va_list *ap)
{
    uint8_t *dstp = base_ptr + par->offset;

    switch (par->type) {
        case PARAM_TYPE_SELECT: {
            int v;
            const char *s = va_arg(*ap, const char *);
            int ret = ngli_params_get_select_val(par->choices->consts, s, &v);
            if (ret < 0) {
                LOG(ERROR, "unrecognized constant \"%s\" for option %s", s, par->key);
                return ret;
            }
            LOG(VERBOSE, "set %s to %s (%d)", par->key, s, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_FLAGS: {
            int v;
            const char *s = va_arg(*ap, const char *);
            int ret = ngli_params_get_flags_val(par->choices->consts, s, &v);
            if (ret < 0) {
                LOG(ERROR, "unrecognized flags \"%s\" for option %s", s, par->key);
                return ret;
            }
            LOG(VERBOSE, "set %s to %s (%d)", par->key, s, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_BOOL:
        case PARAM_TYPE_INT: {
            int v = va_arg(*ap, int);
            if (par->type == PARAM_TYPE_BOOL && v != -1)
                v = !!v;
            LOG(VERBOSE, "set %s to %d", par->key, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_I64: {
            int64_t v = va_arg(*ap, int64_t);
            LOG(VERBOSE, "set %s to %"PRId64, par->key, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_DBL: {
            double v = va_arg(*ap, double);
            LOG(VERBOSE, "set %s to %g", par->key, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_STR: {
            char *s = NULL;
            const char *arg_str = va_arg(*ap, const char *);
            if (!arg_str)
                arg_str = par->def_value.str;
            if (arg_str) {
                s = ngli_strdup(arg_str);
                if (!s)
                    return -1;
                LOG(VERBOSE, "set %s to \"%s\"", par->key, s);
            } else {
                LOG(VERBOSE, "set %s to NULL", par->key);
            }
            ngli_free(*(char **)dstp);
            memcpy(dstp, &s, sizeof(s));
            break;
        }
        case PARAM_TYPE_DATA: {
            int size = va_arg(*ap, int);
            void *data = va_arg(*ap, void *);
            LOG(VERBOSE, "set %s to %p (of size %d)", par->key, data, size);
            uint8_t **dst = (uint8_t **)dstp;

            ngli_free(*dst);
            *dst = NULL;
            if (data && size) {
                *dst = ngli_malloc(size);
                if (!*dst)
                    return -1;
                memcpy(*dst, data, size);
            } else {
                size = 0;
            }
            memcpy(dstp + sizeof(void *), &size, sizeof(size));
            break;
        }
        case PARAM_TYPE_VEC2: {
            const float *v = va_arg(*ap, const float *);
            LOG(VERBOSE, "set %s to (%g,%g)", par->key, v[0], v[1]);
            memcpy(dstp, v, 2 * sizeof(*v));
            break;
        }
        case PARAM_TYPE_VEC3: {
            const float *v = va_arg(*ap, const float *);
            LOG(VERBOSE, "set %s to (%g,%g,%g)", par->key, v[0], v[1], v[2]);
            memcpy(dstp, v, 3 * sizeof(*v));
            break;
        }
        case PARAM_TYPE_VEC4: {
            const float *v = va_arg(*ap, const float *);
            LOG(VERBOSE, "set %s to (%g,%g,%g,%g)", par->key, v[0], v[1], v[2], v[3]);
            memcpy(dstp, v, 4 * sizeof(*v));
            break;
        }
        case PARAM_TYPE_MAT4: {
            const float *v = va_arg(*ap, const float *);
            LOG(VERBOSE,
                "set %s to (%g,%g,%g,%g %g,%g,%g,%g %g,%g,%g,%g %g,%g,%g,%g)",
                par->key,
                v[ 0], v[ 1], v[ 2], v[ 3],
                v[ 4], v[ 5], v[ 6], v[ 7],
                v[ 8], v[ 9], v[10], v[11],
                v[12], v[13], v[14], v[15]);
            memcpy(dstp, v, 16 * sizeof(*v));
            break;
        }
        case PARAM_TYPE_NODE: {
            struct ngl_node *node = va_arg(*ap, struct ngl_node *);
            if (!allowed_node(node, par->node_types)) {
                LOG(ERROR, "%s (%s) is not an allowed type for %s",
                    node->label, node->class->name, par->key);
                return -1;
            }
            ngl_node_unrefp((struct ngl_node **)dstp);
            ngl_node_ref(node);
            LOG(VERBOSE, "set %s to %s", par->key, node->label);
            memcpy(dstp, &node, sizeof(node));
            break;
        }
        case PARAM_TYPE_NODEDICT: {
            int ret;
            const char *name = va_arg(*ap, const char *);
            struct ngl_node *node = va_arg(*ap, struct ngl_node *);
            if (node && !allowed_node(node, par->node_types)) {
                LOG(ERROR, "%s (%s) is not an allowed type for %s",
                    node->label, node->class->name, par->key);
                return -1;
            }
            LOG(VERBOSE, "set %s to (%s,%p)", par->key, name, node);
            struct hmap **hmapp = (struct hmap **)dstp;
            if (!*hmapp) {
                *hmapp = ngli_hmap_create();
                if (!*hmapp)
                    return -1;
                ngli_hmap_set_free(*hmapp, node_hmap_free, NULL);
            }

            ret = ngli_hmap_set(*hmapp, name, node ? ngl_node_ref(node) : NULL);
            if (ret < 0)
                return ret;
            break;
        }
        case PARAM_TYPE_RATIONAL: {
            const int num = va_arg(*ap, int);
            const int den = va_arg(*ap, int);
            LOG(VERBOSE, "set %s to %d/%d", par->key, num, den);
            memcpy(dstp, &num, sizeof(num));
            memcpy(dstp + sizeof(num), &den, sizeof(den));
            break;
        }

    }
    return 0;
}

int ngli_params_vset(uint8_t *base_ptr, const struct node_param *par, ...)
{
    va_list ap;
    va_start(ap, par);
    int ret = ngli_params_set(base_ptr, par, &ap);
    va_end(ap);
    return ret;
}

int ngli_params_set_constructors(uint8_t *base_ptr, const struct node_param *params, va_list *ap)
{
    if (!params)
        return 0;

    for (int i = 0; params[i].key; i++) {
        const struct node_param *par = &params[i];<commit_msg>params: reference dict node only on insertion success<commit_after>/*
 * Copyright 2016 GoPro Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <string.h>
#include <inttypes.h>

#include "bstr.h"
#include "log.h"
#include "hmap.h"
#include "memory.h"
#include "nodegl.h"
#include "nodes.h"
#include "params.h"
#include "utils.h"

const struct param_specs ngli_params_specs[] = {
    [PARAM_TYPE_INT] = {
        .name = "int",
        .size = sizeof(int),
        .desc = NGLI_DOCSTRING("Integer"),
    },
    [PARAM_TYPE_BOOL] = {
        .name = "bool",
        .size = sizeof(int),
        .desc = NGLI_DOCSTRING("Boolean (map to `int` in C)"),
    },
    [PARAM_TYPE_I64] = {
        .name = "i64",
        .size = sizeof(int64_t),
        .desc = NGLI_DOCSTRING("64-bit integer"),
    },
    [PARAM_TYPE_DBL] = {
        .name = "double",
        .size = sizeof(double),
        .desc = NGLI_DOCSTRING("Double-precision float"),
    },
    [PARAM_TYPE_STR] = {
        .name = "string",
        .size = sizeof(char *),
        .desc = NGLI_DOCSTRING("String"),
    },
    [PARAM_TYPE_DATA] = {
        .name = "data",
        .size = sizeof(void *) + sizeof(int),
        .desc = NGLI_DOCSTRING("Agnostic data buffer"),
    },
    [PARAM_TYPE_VEC2] = {
        .name = "vec2",
        .size = sizeof(float[2]),
        .desc = NGLI_DOCSTRING("2 single-precision floats"),
    },
    [PARAM_TYPE_VEC3] = {
        .name = "vec3",
        .size = sizeof(float[3]),
        .desc = NGLI_DOCSTRING("3 single-precision floats"),
    },
    [PARAM_TYPE_VEC4] = {
        .name = "vec4",
        .size = sizeof(float[4]),
        .desc = NGLI_DOCSTRING("4 single-precision floats"),
    },
    [PARAM_TYPE_MAT4] = {
        .name = "mat4",
        .size = sizeof(float[4*4]),
        .desc = NGLI_DOCSTRING("4x4 single-precision floats"),
    },
    [PARAM_TYPE_NODE] = {
        .name = "Node",
        .size = sizeof(struct ngl_node *),
        .desc = NGLI_DOCSTRING("node.gl Node"),
    },
    [PARAM_TYPE_NODELIST] = {
        .name = "NodeList",
        .size = sizeof(struct ngl_node **) + sizeof(int),
        .desc = NGLI_DOCSTRING("List of node.gl Node"),
    },
    [PARAM_TYPE_DBLLIST] = {
        .name = "doubleList",
        .size = sizeof(double *) + sizeof(int),
        .desc = NGLI_DOCSTRING("List of double-precision floats"),
    },
    [PARAM_TYPE_NODEDICT] = {
        .name = "NodeDict",
        .size = sizeof(struct hmap *),
        .desc = NGLI_DOCSTRING("Dictionary mapping arbitrary string identifiers to node.gl Nodes"),
    },
    [PARAM_TYPE_SELECT] = {
        .name = "select",
        .size = sizeof(int),
        .desc = NGLI_DOCSTRING("Selection of one constant (expressed as a string)"),
    },
    [PARAM_TYPE_FLAGS] = {
        .name = "flags",
        .size = sizeof(int),
        .desc = NGLI_DOCSTRING("Combination of constants (expressed as strings), using `+` as separator. Can be empty for none."),
    },
    [PARAM_TYPE_RATIONAL] = {
        .name = "rational",
        .size = sizeof(int[2]),
        .desc = NGLI_DOCSTRING("Rational number (expressed as 2 integers, respectively as numerator and denominator)"),
    },
};

const struct node_param *ngli_params_find(const struct node_param *params, const char *key)
{
    if (!params)
        return NULL;
    for (int i = 0; params[i].key; i++) {
        const struct node_param *par = &params[i];
        if (!strcmp(par->key, key))
            return par;
    }
    return NULL;
}

int ngli_params_get_select_val(const struct param_const *consts, const char *s, int *dst)
{
    for (int i = 0; consts[i].key; i++) {
        if (!strcmp(consts[i].key, s)) {
            *dst = consts[i].value;
            return 0;
        }
    }
    return -1;
}

const char *ngli_params_get_select_str(const struct param_const *consts, int val)
{
    for (int i = 0; consts[i].key; i++)
        if (consts[i].value == val)
            return consts[i].key;
    return NULL;
}

/* The first separator (`+`) is used as default separator because `|` can not
 * be used due to markdown table formatting, and ` ` will cause parsing issue
 * in the serialization.
 */
#define FLAGS_SEP "+| "

int ngli_params_get_flags_val(const struct param_const *consts, const char *s, int *dst)
{
    *dst = 0;
    if (!strcmp(s, "0"))
        return 0;
    if (!strcmp(s, "unset")) {
        *dst = -1;
        return 0;
    }
    while (*s) {
        int i;
        const size_t len = strcspn(s, FLAGS_SEP);
        for (i = 0; consts[i].key; i++) {
            if (!strncmp(consts[i].key, s, len)) {
                /* -1 is a reserved value and means that the mask is unset */
                ngli_assert(consts[i].value != -1);
                *dst |= consts[i].value;
                break;
            }
        }
        if (!consts[i].key) {
            LOG(ERROR, "unrecognized \"%.*s\" flag", (int)len, s);
            return -1;
        }
        s += len;
        while (*s && strchr(FLAGS_SEP, *s))
            s++;
    }
    return 0;
}

char *ngli_params_get_flags_str(const struct param_const *consts, int val)
{
    if (!val)
        return ngli_strdup("0");

    if (val == -1)
        return ngli_strdup("unset");

    struct bstr *b = ngli_bstr_create();
    if (!b)
        return NULL;

    for (int i = 0; consts[i].key; i++)
        if (val & consts[i].value)
            ngli_bstr_print(b, "%.1s%s", *ngli_bstr_strptr(b) ? FLAGS_SEP : "", consts[i].key);

    char *ret = ngli_bstr_strdup(b);
    ngli_bstr_freep(&b);
    return ret;
}

void ngli_params_bstr_print_val(struct bstr *b, uint8_t *base_ptr, const struct node_param *par)
{
    switch (par->type) {
        case PARAM_TYPE_DBL: {
            const double v = *(double *)(base_ptr + par->offset);
            ngli_bstr_print(b, "%g", v);
            break;
        }
        case PARAM_TYPE_SELECT: {
            const int v = *(int *)(base_ptr + par->offset);
            const char *s = ngli_params_get_select_str(par->choices->consts, v);
            ngli_assert(s);
            ngli_bstr_print(b, "%s", s);
            break;
        }
        case PARAM_TYPE_FLAGS: {
            const int v = *(int *)(base_ptr + par->offset);
            char *s = ngli_params_get_flags_str(par->choices->consts, v);
            if (!s)
                break;
            ngli_assert(*s);
            ngli_bstr_print(b, "%s", s);
            ngli_free(s);
            break;
        }
        case PARAM_TYPE_BOOL: {
            const int v = *(int *)(base_ptr + par->offset);
            if (v == -1)
                ngli_bstr_print(b, "unset");
            else
                ngli_bstr_print(b, "%d", v);
            break;
        }
        case PARAM_TYPE_INT: {
            const int v = *(int *)(base_ptr + par->offset);
            ngli_bstr_print(b, "%d", v);
            break;
        }
        case PARAM_TYPE_I64: {
            const int64_t v = *(int64_t *)(base_ptr + par->offset);
            ngli_bstr_print(b, "%" PRId64, v);
            break;
        }
        case PARAM_TYPE_VEC2: {
            const float *v = (const float *)(base_ptr + par->offset);
            ngli_bstr_print(b, "(%g,%g)", v[0], v[1]);
            break;
        }
        case PARAM_TYPE_VEC3: {
            const float *v = (const float *)(base_ptr + par->offset);
            ngli_bstr_print(b, "(%g,%g,%g)", v[0], v[1], v[2]);
            break;
        }
        case PARAM_TYPE_VEC4: {
            const float *v = (const float *)(base_ptr + par->offset);
            ngli_bstr_print(b, "(%g,%g,%g,%g)", v[0], v[1], v[2], v[3]);
            break;
        }
        case PARAM_TYPE_MAT4: {
            const float *m = (const float *)(base_ptr + par->offset);
            ngli_bstr_print(b,
                            "(%g,%g,%g,%g %g,%g,%g,%g %g,%g,%g,%g %g,%g,%g,%g)",
                            m[ 0], m[ 1], m[ 2], m[ 3],
                            m[ 4], m[ 5], m[ 6], m[ 7],
                            m[ 8], m[ 9], m[10], m[11],
                            m[12], m[13], m[14], m[15]);
            break;
        }
        case PARAM_TYPE_STR: {
            const char *s = *(const char **)(base_ptr + par->offset);
            if (!s)
                ngli_bstr_print(b, "\"\"");
            else if (strchr(s, '\n')) // print a checksum when the string is multiline (typically, shaders)
                ngli_bstr_print(b, "%08X <i>(CRC32)</i>", ngli_crc32(s));
            else if (strchr(s, '/')) // assume a file and display only basename
                ngli_bstr_print(b, "\"%s\"", strrchr(s, '/') + 1);
            else
                ngli_bstr_print(b, "\"%s\"", s);
            break;
        }
        case PARAM_TYPE_DBLLIST: {
            uint8_t *elems_p = base_ptr + par->offset;
            uint8_t *nb_elems_p = base_ptr + par->offset + sizeof(double *);
            const double *elems = *(double **)elems_p;
            const int nb_elems = *(int *)nb_elems_p;
            for (int i = 0; i < nb_elems; i++)
                ngli_bstr_print(b, "%s%g", i ? "," : "", elems[i]);
            break;
        }
        case PARAM_TYPE_RATIONAL: {
            const int *r = (int *)(base_ptr + par->offset);
            ngli_bstr_print(b, "%d/%d", r[0], r[1]);
            break;
        }
    }
}

static int allowed_node(const struct ngl_node *node, const int *allowed_ids)
{
    if (!allowed_ids)
        return 1;
    const int id = node->class->id;
    for (int i = 0; allowed_ids[i] != -1; i++)
        if (id == allowed_ids[i])
            return 1;
    return 0;
}

static void node_hmap_free(void *user_arg, void *data)
{
    struct ngl_node *node = data;
    ngl_node_unrefp(&node);
}

int ngli_params_set(uint8_t *base_ptr, const struct node_param *par, va_list *ap)
{
    uint8_t *dstp = base_ptr + par->offset;

    switch (par->type) {
        case PARAM_TYPE_SELECT: {
            int v;
            const char *s = va_arg(*ap, const char *);
            int ret = ngli_params_get_select_val(par->choices->consts, s, &v);
            if (ret < 0) {
                LOG(ERROR, "unrecognized constant \"%s\" for option %s", s, par->key);
                return ret;
            }
            LOG(VERBOSE, "set %s to %s (%d)", par->key, s, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_FLAGS: {
            int v;
            const char *s = va_arg(*ap, const char *);
            int ret = ngli_params_get_flags_val(par->choices->consts, s, &v);
            if (ret < 0) {
                LOG(ERROR, "unrecognized flags \"%s\" for option %s", s, par->key);
                return ret;
            }
            LOG(VERBOSE, "set %s to %s (%d)", par->key, s, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_BOOL:
        case PARAM_TYPE_INT: {
            int v = va_arg(*ap, int);
            if (par->type == PARAM_TYPE_BOOL && v != -1)
                v = !!v;
            LOG(VERBOSE, "set %s to %d", par->key, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_I64: {
            int64_t v = va_arg(*ap, int64_t);
            LOG(VERBOSE, "set %s to %"PRId64, par->key, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_DBL: {
            double v = va_arg(*ap, double);
            LOG(VERBOSE, "set %s to %g", par->key, v);
            memcpy(dstp, &v, sizeof(v));
            break;
        }
        case PARAM_TYPE_STR: {
            char *s = NULL;
            const char *arg_str = va_arg(*ap, const char *);
            if (!arg_str)
                arg_str = par->def_value.str;
            if (arg_str) {
                s = ngli_strdup(arg_str);
                if (!s)
                    return -1;
                LOG(VERBOSE, "set %s to \"%s\"", par->key, s);
            } else {
                LOG(VERBOSE, "set %s to NULL", par->key);
            }
            ngli_free(*(char **)dstp);
            memcpy(dstp, &s, sizeof(s));
            break;
        }
        case PARAM_TYPE_DATA: {
            int size = va_arg(*ap, int);
            void *data = va_arg(*ap, void *);
            LOG(VERBOSE, "set %s to %p (of size %d)", par->key, data, size);
            uint8_t **dst = (uint8_t **)dstp;

            ngli_free(*dst);
            *dst = NULL;
            if (data && size) {
                *dst = ngli_malloc(size);
                if (!*dst)
                    return -1;
                memcpy(*dst, data, size);
            } else {
                size = 0;
            }
            memcpy(dstp + sizeof(void *), &size, sizeof(size));
            break;
        }
        case PARAM_TYPE_VEC2: {
            const float *v = va_arg(*ap, const float *);
            LOG(VERBOSE, "set %s to (%g,%g)", par->key, v[0], v[1]);
            memcpy(dstp, v, 2 * sizeof(*v));
            break;
        }
        case PARAM_TYPE_VEC3: {
            const float *v = va_arg(*ap, const float *);
            LOG(VERBOSE, "set %s to (%g,%g,%g)", par->key, v[0], v[1], v[2]);
            memcpy(dstp, v, 3 * sizeof(*v));
            break;
        }
        case PARAM_TYPE_VEC4: {
            const float *v = va_arg(*ap, const float *);
            LOG(VERBOSE, "set %s to (%g,%g,%g,%g)", par->key, v[0], v[1], v[2], v[3]);
            memcpy(dstp, v, 4 * sizeof(*v));
            break;
        }
        case PARAM_TYPE_MAT4: {
            const float *v = va_arg(*ap, const float *);
            LOG(VERBOSE,
                "set %s to (%g,%g,%g,%g %g,%g,%g,%g %g,%g,%g,%g %g,%g,%g,%g)",
                par->key,
                v[ 0], v[ 1], v[ 2], v[ 3],
                v[ 4], v[ 5], v[ 6], v[ 7],
                v[ 8], v[ 9], v[10], v[11],
                v[12], v[13], v[14], v[15]);
            memcpy(dstp, v, 16 * sizeof(*v));
            break;
        }
        case PARAM_TYPE_NODE: {
            struct ngl_node *node = va_arg(*ap, struct ngl_node *);
            if (!allowed_node(node, par->node_types)) {
                LOG(ERROR, "%s (%s) is not an allowed type for %s",
                    node->label, node->class->name, par->key);
                return -1;
            }
            ngl_node_unrefp((struct ngl_node **)dstp);
            ngl_node_ref(node);
            LOG(VERBOSE, "set %s to %s", par->key, node->label);
            memcpy(dstp, &node, sizeof(node));
            break;
        }
        case PARAM_TYPE_NODEDICT: {
            int ret;
            const char *name = va_arg(*ap, const char *);
            struct ngl_node *node = va_arg(*ap, struct ngl_node *);
            if (node && !allowed_node(node, par->node_types)) {
                LOG(ERROR, "%s (%s) is not an allowed type for %s",
                    node->label, node->class->name, par->key);
                return -1;
            }
            LOG(VERBOSE, "set %s to (%s,%p)", par->key, name, node);
            struct hmap **hmapp = (struct hmap **)dstp;
            if (!*hmapp) {
                *hmapp = ngli_hmap_create();
                if (!*hmapp)
                    return -1;
                ngli_hmap_set_free(*hmapp, node_hmap_free, NULL);
            }

            ret = ngli_hmap_set(*hmapp, name, node);
            if (ret < 0)
                return ret;
            if (node)
                ngl_node_ref(node);
            break;
        }
        case PARAM_TYPE_RATIONAL: {
            const int num = va_arg(*ap, int);
            const int den = va_arg(*ap, int);
            LOG(VERBOSE, "set %s to %d/%d", par->key, num, den);
            memcpy(dstp, &num, sizeof(num));
            memcpy(dstp + sizeof(num), &den, sizeof(den));
            break;
        }

    }
    return 0;
}

int ngli_params_vset(uint8_t *base_ptr, const struct node_param *par, ...)
{
    va_list ap;
    va_start(ap, par);
    int ret = ngli_params_set(base_ptr, par, &ap);
    va_end(ap);
    return ret;
}

int ngli_params_set_constructors(uint8_t *base_ptr, const struct node_param *params, va_list *ap)
{
    if (!params)
        return 0;

    for (int i = 0; params[i].key; i++) {
        const struct node_param *par = &params[i];
{'fraction_non_alphanumeric': 0.1501094609786403, 'fraction_numerical': 0.01088692976746938, 'mean_word_length': 1.727148043565954, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 127, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2926135', 'n_tokens_mistral': 11832, 'n_tokens_neox': 11365, 'n_words': 2873}