#!/opt/local/bin/perl5.34 -w

my $license = <<EOF;
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2021  Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
EOF

use strict;

use Conf::Libconfig;
use Getopt::Long;

use Data::Dumper;

my $date = scalar localtime;
my $banner = <<EOF;
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
 * on $date. 

$license
*/
EOF


my $conf = Conf::Libconfig->new;
my $filename = shift @ARGV;
die "Usage:\n\tconf2struct <file.cfg>\n" unless defined $filename;
$conf->read_file($filename) or die "$filename: $!\n";

my $header = $conf->lookup_value("header");
my $parser = $conf->lookup_value("parser");
my $printer = $conf->lookup_value("printer") // 0;

die "$filename: Setting for header filename is not defined\n" unless defined $header;
die "$filename: Setting for parser filename is not defined\n" unless defined $parser;

my $hr = {
    header              => $header,
    parser              => $parser,
    printer             => $printer,
    conffile_option     => $conf->fetch_array("conffile_option"),
    includes            => $conf->fetch_array("includes"),
    config              => $conf->fetch_hashref("config"),
    cl_groups           => $conf->fetch_array("cl_groups"),
};

# a bit of vocabulary:
# Schema: libconfig input file to the perl script which
#         describes the data contained in the target configuration
# Descriptors: C structures that describe the C target configuration (generated by c2s)
#       basically, one descriptor per field in the target structure
# Config: libconfig input file to the C program
# Target setting/config: C structs used by the final program, described in .h (*_item)
# (but most C here doesn't know about it, so only refers to it throught descriptors)


#                                     Target settings
# schema -> c2s.pl -> c2s.c        /-> scalars
#                    descriptors --+-> groups (struct)
#                      ^           \-> lists (array of struct)
#                      |
#                   config

# This vocabulary is not (yet) consistent across c2s as it
# took me a while to work it out...



################################################################################
# Turns a libconfig type to a C type or undef if unknown type
sub scalar_type {
    my ($in) = @_;
    my $out = {
        'bool' => 'int',
        'int' => 'int',
        'int64' => 'long long',
        'float' => 'double',
        'string' => 'char*',
    }->{$in};
    die "Unknown type `$in'\n" unless defined $out;
    return $out;
}

# Return true if setting is a scalar
sub is_scalar {
    my ($s) = @_;
    if ($s->{type} =~ /bool|int|int64|float|string/) {
        return 1;
    } else {
        return 0;
    }
}

################################################################################
# Returns the printf format to print the libconfig type
sub scalar_type_print {
    my ($in) = @_;
    {
        'bool' => '%d',
        'int' => '%d',
        'int64' => "%lld",
        'float' => "%lf",
        'string' => '%s',
    }->{$in};
}

################################################################################
# Turn libconfig setting names to C struct names
sub cfgname2c {
    my ($name) = @_;
    $name =~ s/[-.]/_/g;
    return $name;
}

################################################################################
# C code included in output

my %snippets = (
header => <<'EOF',

/* This gets included in the output .c file */


/* Libconfig 1.4.9 is still used by major distributions
 * (e.g. CentOS7) and had a different name for
 * config_setting_lookup */
#if LIBCONFIG_VER_MAJOR == 1
#if LIBCONFIG_VER_MINOR == 4
#if LIBCONFIG_VER_REVISION == 9
#define config_setting_lookup config_lookup_from
#endif
#endif
#endif


/* config_type, lookup_fns, type2str are related, keep them together */
typedef enum {
    CFG_BOOL,
    CFG_INT,
    CFG_INT64,
    CFG_FLOAT,
    CFG_STRING,
    CFG_GROUP,
    CFG_ARRAY,
    CFG_LIST,
} config_type;
/* /config_type */

const char* type2str[] = {
    "boolean",
    "int",
    "int64",
    "float",
    "string",
    "group",
    "array",
    "list",
};

typedef union {
    int def_bool;
    int def_int;
    long long def_int64;
    double def_float;
    char* def_string;
} any_val;

struct config_desc {
    const char* name;
    int type;
    struct config_desc * sub_group; /* Table for compound types (list and group) */
    void* arg_cl; /* command-line argument for this setting */
    void* base_addr; /* Base of the structure (filled at runtime). Probably not useable for list elements */
    size_t offset;  /* Offset of setting in the structure */
    size_t offset_len; /* Offset of *_len field, for arrays and lists */
    size_t offset_present; /* offset of *_is_present field, for optional settings */
    size_t size;   /* Size of element, or size of group for groups and lists */
    int array_type; /* type of array elements, when type == CFG_ARRAY */
    int mandatory;
    int optional;
    any_val default_val;
};

#ifndef LIBCONFIG
/* Stubs in case you don't want libconfig */

typedef void config_setting_t;
typedef int config_t;
#define CONFIG_TRUE 1
#define CONFIG_FALSE 0

#define make_config_setting_lookup(type) \
    int config_setting_lookup_##type(const config_setting_t* a, const char* b, void* c) { \
        return 0; \
    }

#define make_config_setting_get(type, ret_type) \
    ret_type config_setting_get_##type(const config_setting_t* a) { \
        return 0; \
    }

enum {
    CONFIG_TYPE_INT,
    CONFIG_TYPE_BOOL,
    CONFIG_TYPE_INT64,
    CONFIG_TYPE_FLOAT,
    CONFIG_TYPE_STRING
};

make_config_setting_lookup(bool);
make_config_setting_lookup(int);
make_config_setting_lookup(int64);
make_config_setting_lookup(float);
make_config_setting_lookup(string);

make_config_setting_get(bool, int);
make_config_setting_get(int, int);
make_config_setting_get(int64, long long int);
make_config_setting_get(float, double);
make_config_setting_get(string, char*);

config_setting_t* config_root_setting(config_t* c) {
    return NULL;
}

config_setting_t* config_lookup(config_t* c, const char* b) {
    return NULL;
}

void config_init(config_t* c) {
    return;
}

char* config_setting_name(config_setting_t* c) {
    return NULL;
}

int config_setting_is_list(config_setting_t* c) {
    return 0;
}

int config_setting_is_array(config_setting_t* c) {
    return 0;
}

int config_setting_is_scalar(config_setting_t* c) {
    return 0;
}

int config_setting_index(const config_setting_t *setting) {
    return 0;
}

config_setting_t* config_setting_lookup(config_setting_t* a, char* b) {
    return NULL;
}

int config_setting_remove(config_setting_t* cfg, char* name) {
    return 0;
}

int config_setting_type(config_setting_t* s) {
    return -1;
}

int config_setting_length(config_setting_t* a) {
    return 0;
}

config_setting_t* config_setting_get_elem(config_setting_t* a, int i) {
    return NULL;
}

int config_read_file(config_t* a, const char* b) {
    return CONFIG_TRUE;
}

int config_error_line(config_t* c) {
    return 0;
}

char* config_error_text(config_t* c) {
    return NULL;
}
#endif

/* This is the same as config_setting_lookup_string() except
it allocates a new string which belongs to the caller */
static int myconfig_setting_lookup_stringcpy(
        const config_setting_t* setting, 
        const char* name, 
        char** value)
{
    const char* str;
    *value = NULL;
    if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) {
        asprintf(value, "%s", str);
        return CONFIG_TRUE;
    } else {
        return CONFIG_FALSE;
    }
}

typedef int (*lookup_fn)(const config_setting_t*, const char*, void*);
lookup_fn lookup_fns[] = {
    (lookup_fn)config_setting_lookup_bool,
    (lookup_fn)config_setting_lookup_int,
    (lookup_fn)config_setting_lookup_int64,
    (lookup_fn)config_setting_lookup_float,
    (lookup_fn)myconfig_setting_lookup_stringcpy,
    NULL,  /* CFG_GROUP */
    NULL,  /* CFG_ARRAY */
    NULL,  /* CFG_LIST */
};

/* Copy an any_val to arbitrary memory location */
/* 0: success
 * <0: error */
static int any_valcpy(config_type type, void* target, any_val val)
{
    switch(type) {
    case CFG_BOOL:
        *(int*)target = val.def_bool;
        break;

    case CFG_INT:
        *(int*)target = val.def_int;
        break;

    case CFG_INT64:
        *(long long*)target = val.def_int64;
        break;

    case CFG_FLOAT:
        *(double*)target = val.def_float;
        break;

    case CFG_STRING:
        *(char**)target = val.def_string;
        break;

    default:
        fprintf(stderr, "Unknown type specification %d\n", type);
        return -1;
    }
    return 1;
}


/* Copy the value of a setting to an arbitrary memory that
* must be large enough */
/* 0: success
 * <0: error */
static int settingcpy(config_type type, void* target, const config_setting_t* setting)
{
    any_val val;
    char* str;

    switch(type) {
    case CFG_BOOL:
        val.def_bool = config_setting_get_bool(setting);
        *(int*)target = val.def_bool;
        break;

    case CFG_INT:
        val.def_int = config_setting_get_int(setting);
        *(int*)target = val.def_int;
        break;

    case CFG_INT64:
        val.def_int64 = config_setting_get_int64(setting);
        *(long long*)target = val.def_int64;
        break;

    case CFG_FLOAT:
        val.def_float = config_setting_get_float(setting);
        *(double*)target = val.def_int64;
        break;

    case CFG_STRING:
        asprintf(&str, "%s", config_setting_get_string(setting));
        val.def_string = str;
        *(char**)target = val.def_string;
        break;

    default:
        fprintf(stderr, "Unknown type specification %d\n", type);
        return -1;
    }
    return 0;
}

/* Copy the value of a command line arg to arbitrary memory
* that must be large enough for the type */
/* 0: success
 * <0: error */
static int clcpy(config_type type, void* target, const void* cl_arg)
{
    any_val val;
    char* str;

    switch(type) {
    case CFG_BOOL:
        val.def_bool = (*(struct arg_lit**)cl_arg)->count;
        *(int*)target = val.def_bool;
        break;

    case CFG_INT:
        val.def_int = (*(struct arg_int**)cl_arg)->ival[0];
        *(int*)target = val.def_int;
        break;

    case CFG_INT64:
        val.def_int64 = (*(struct arg_int**)cl_arg)->ival[0];
        *(long long*)target = val.def_int64;
        break;

    case CFG_FLOAT:
        val.def_float = (*(struct arg_dbl**)cl_arg)->dval[0];
        *(double*)target = val.def_float;
        break;

    case CFG_STRING:
        asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
        val.def_string = str;
        *(char**)target = val.def_string;
        break;

    default:
        fprintf(stderr, "Unknown type specification %d\n", type);
        return -1;
    }
    return 0;
}

/* Copy the value of a string argument to arbitary memory
* location that must be large enough, converting on the way
* (i.e. CFG_INT gets atoi() and so on) */
/* 0: success
 * <0: error */
static int stringcpy(config_type type, void* target, char* from)
{
    any_val val;
    
    switch(type) {
    case CFG_BOOL:
        val.def_bool = (*from != '0');
        *(int*)target = val.def_bool;
        break;

    case CFG_INT:
        val.def_int = strtol(from, NULL, 10);
        *(int*)target = val.def_int;
        break;

    case CFG_INT64:
        val.def_int64 = strtoll(from, NULL, 10);
        *(long long*)target = val.def_int64;
        break;

    case CFG_FLOAT:
        val.def_float = strtod(from, NULL);
        *(double*)target = val.def_float;
        break;

    case CFG_STRING:
        val.def_string = from;
        *(char**)target = val.def_string;
        break;

    default:
        fprintf(stderr, "Unknown type specification %d\n", type);
        return -1;
    }
    return 0;
}


/* Element to describe the target of a compound element
* element: which config entry is being changed
* match: if >0, index in pmatch to set
*        if 0, don't match but init with value
* value: constant if not matching */
struct compound_cl_target {
    struct config_desc * element;
    int match;
    any_val value;
};

/* Element to describe one compound command line argument
 * An argument is string that gets matched against a regex,
 * then match-groups get evaluated to each targets[].
 * For lists, base_entry points to the config_setting so we
 * can append to it */
struct compound_cl_arg {
    const char* regex;
    struct arg_str** arg_cl; /* arg_str entry for this compound option */
    struct config_desc * base_entry;
    struct compound_cl_target* targets;

    /* If override_desc is set, it points to the descriptor of the element in
    the group which will be checked for override. Then, override_matchindex
    indicates the command-line parameter match used to compare against
    override_desc to know if this group is overridden. If override_matchindex
    is 0, we don't match from the command-line but from a constant stored in
    override_const instead */
    struct config_desc * override_desc;
    int override_matchindex;
    char* override_const;
};


EOF


c_read_block => <<'EOF',

/* Enable debug to follow the parsing of tables */
#if 0
#define TRACE_READ(x) printf x
#define TRACE_READ_PRINT_SETTING 1
#else
#define TRACE_READ(x)
#define TRACE_READ_PRINT_SETTING 0
#endif

/* Enable debug to follow the parsing of compound options */
#if 0
#define TRACE_CMPD(x) printf x
#define TRACE_CMPD_PRINT_SETTING 1
#else
#define TRACE_CMPD(x)
#define TRACE_CMPD_PRINT_SETTING 0
#endif

static void print_setting(config_type type, void* val)
{
    if (TRACE_READ_PRINT_SETTING || TRACE_CMPD_PRINT_SETTING) {
        switch(type) {
        case CFG_BOOL:
        case CFG_INT:
            printf("%d", *(int*)val);
            break;
        case CFG_INT64:
            printf("%lld", *(long long*)val);
            break;
        case CFG_FLOAT:
            printf("%f", *(double*)val);
            break;
        case CFG_STRING:
            printf("`%s'", *(char**)val);
            break;
        case CFG_GROUP:
        case CFG_LIST:
        case CFG_ARRAY:
            break;
        }
    }
}

/* Changes all dashes to underscores in a string of
* vice-versa */
static void strswap_ud(const char target, char* str)
{
    char* c;
    for (c = str; *c; c++)
        if (*c == (target == '_' ? '-' : '_'))
             *c = (target == '_' ? '_' : '-');
}

/* Same as config_setting_lookup() but looks up with dash or
* underscore so `my_setting` and `my-setting` match the same */
static config_setting_t* config_setting_lookup_ud(config_setting_t* cfg, struct config_desc* desc)
{
    config_setting_t* setting;
    char name[strlen(desc->name)+1];;
    strcpy(name, desc->name);

    strswap_ud('_', name);
    setting = config_setting_lookup(cfg, name);
    if (setting)
        return setting;

    strswap_ud('-', name);
    setting = config_setting_lookup(cfg, name);
    return setting;
}

static int lookup_typed_ud(config_setting_t* cfg, void* target, struct config_desc *desc)
{
    lookup_fn lookup_fn = lookup_fns[desc->type];
    char name[strlen(desc->name)+1];;
    strcpy(name, desc->name);

    strswap_ud('_', name);
    if (lookup_fn(cfg, name, ((char*)target) + desc->offset) == CONFIG_TRUE)
        return CONFIG_TRUE;

    strswap_ud('-', name);
    return lookup_fn(cfg, name, ((char*)target) + desc->offset);
}

/* Removes a setting, trying both underscores and dashes as
* name (so deleting 'my-setting' deletes both 'my_setting'
* and 'my-setting') */
static int setting_delete_ud(config_setting_t* cfg, struct config_desc *desc)
{
    char name[strlen(desc->name)+1];;
    strcpy(name, desc->name);

    strswap_ud('_', name);
    if (config_setting_remove(cfg, name) == CONFIG_TRUE)
        return CONFIG_TRUE;

    strswap_ud('-', name);
    return config_setting_remove(cfg, name);
}

/* When traversing configuration, allocate memory for plural
* types, init for scalars */
static void read_block_init(void* target, config_setting_t* cfg, struct config_desc* desc)
{
    size_t len = 0;
    void* block;
    config_setting_t* setting;

    switch (desc->type) {
    case CFG_LIST:
    case CFG_ARRAY:
        if (cfg) {
            setting = config_setting_lookup_ud(cfg, desc);
            if (setting)
                len = config_setting_length(setting);
        } 
        block = calloc(len, desc->size);

        *(size_t*)(((char*)target) + desc->offset_len) = len;
        *(void**)(((char*)target) + desc->offset) = block;
        TRACE_READ((" sizing for %zu elems ", len));
        break;

    case CFG_GROUP:
        block = calloc(1, desc->size);
        *(void**)(((char*)target) + desc->offset) = block;
        TRACE_READ((" sizing for %zu elems ", len));
        break;

    default: 
        /* scalar types: copy default */
        memcpy(((char*)target) + desc->offset, &desc->default_val, desc->size);
        TRACE_READ(("setting %s to default ", desc->name));
        print_setting(desc->type,(char*)target + desc->offset);
        break;
    }
}

static int read_block(config_setting_t* cfg, 
                      void* target, 
                      struct config_desc* desc, 
                      char** errmsg);

/* When traversing configuration, set value from config
* file, or command line 
* return: 0 if not set, 1 if set somehow */
static int read_block_setval(void* target, 
                             config_setting_t* cfg, 
                             struct config_desc* desc, 
                             char** errmsg)
{
    int i;
    size_t len = 0;
    void* block;
    int in_cfg = 0, in_cl = 0; /* Present in config file?  present on command line? */
    config_setting_t* setting = NULL;

    switch (desc->type) {
    case CFG_LIST:
        if (cfg) {
            setting = config_setting_lookup_ud(cfg, desc);
            if (setting) 
                len = config_setting_length(setting);
            block = *(void**)(((char*)target) + desc->offset);
            for (i = 0; i < len; i++) {
                config_setting_t* elem = config_setting_get_elem(setting, i);
                if (!read_block(elem, (char*)block + desc->size * i, desc->sub_group, errmsg))
                    return 0;
            }
        }
        break;

    case CFG_ARRAY:
        if (cfg) {
            setting = config_setting_lookup_ud(cfg, desc);
            if (setting)
                len = config_setting_length(setting);
            block = *(void**)(((char*)target) + desc->offset);
            for (i = 0; i < len; i++) {
                config_setting_t* elem = config_setting_get_elem(setting, i);
                settingcpy(desc->array_type, (char*)block + desc->size * i, elem);
                TRACE_READ(("[%d] = ", i));
                print_setting(desc->array_type, (char*)block + desc->size *i); TRACE_READ(("\n"));
            }
            setting_delete_ud(cfg, desc);
        }
        break;

    case CFG_GROUP:
        if (cfg) setting = config_setting_lookup_ud(cfg, desc);
        block = *(void**)(((char*)target) + desc->offset);
        if (!read_block(setting, block, desc->sub_group, errmsg)) return 0;
        break;

    default: /* scalar types */
        TRACE_READ((" `%s'", desc->name));
        if (cfg && config_setting_lookup_ud(cfg, desc)) {
            TRACE_READ((" in config file: "));
            /* setting is present in cfg, look it up */
            if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) {
                TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type]));
                asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n", 
                    desc->name, type2str[desc->type]);
                return 0;
            }
            print_setting(desc->type, (((char*)target) + desc->offset));
            setting_delete_ud(cfg, desc);
            in_cfg = 1;
        } else {
            TRACE_READ((" not in config file"));
        }
        if (desc->arg_cl && (*(struct arg_int**)desc->arg_cl)->count) {
            clcpy(desc->type, ((char*)target) + desc->offset, desc->arg_cl);
            TRACE_READ((", command line sets to "));
            print_setting(desc->type, (((char*)target) + desc->offset));
            in_cl = 1;
        } else {
            TRACE_READ((", not in command line"));
        }
        if (!(in_cfg || in_cl)) {
            TRACE_READ(("\n"));
            return 0;
        }
        TRACE_READ(("\n"));
        break;
    }
    return 1;
}

/* Set *_is_present flag for target */
static void target_is_present(void* target, struct config_desc* desc, int val)
{
    if (desc->optional) {  /* _is_present only exists in target for optional settings */
        TRACE_READ((" mark as set"));
        *(int*)((char*)target + desc->offset_present) = val;
    }
}

/* traverses the configuration; allocates memory if needed,
* set to default if exists,
* fill from configuration file if present, overrides or set from
* command line if present, verifies mandatory options have
* been set
* target: base address of the group being processed
*/
static int read_block(config_setting_t* cfg, void* target, struct config_desc* desc, char** errmsg)
{
    int set;

    for (; desc->name; desc++) {
        TRACE_READ(("reading %s%s%s: ", desc->optional ? "optional " : "", desc->mandatory ? "mandatory " : "",  desc->name));
        desc->base_addr = target;


        read_block_init(target, cfg, desc);
        set = read_block_setval(target, cfg, desc, errmsg);

        if (!set && desc->mandatory) {
            asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
            return 0;
        }

        if (desc->optional) target_is_present(target, desc, set && desc->optional);
    }
    return 1;
}

/* Copy regex match into newly allocated string
 * out: newly allocated string (caller has to free it)
 * in: string into which the match was made
 * pmatch: the match to extract */
static void pmatchcpy(char** out, const char* in, regmatch_t* pmatch)
{
    int len = pmatch->rm_eo - pmatch->rm_so;
    *out = calloc(len+1, 1);
    memcpy(*out, in + pmatch->rm_so, len);
}

/* Processes a list of targets within one element, setting
* the values in the target setting 
* target: where to put the data
* arg: CL arg containing the target fields
* clval: command line parameter
* pmatch: regex match array into clval
*/
static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, const char* clval, regmatch_t* pmatch)
{
    int pmatch_cnt = 1;
    struct compound_cl_target* target;

    for (target = arg->targets; target->element; target++) {
        struct config_desc * element = target->element;
        if (target->match) {
            TRACE_CMPD(("    match %d rm_so %d rm_eo %d type %d\n", 
                        pmatch_cnt, pmatch[pmatch_cnt].rm_so, pmatch[pmatch_cnt].rm_eo, element->type ));
            if (pmatch[pmatch_cnt].rm_so == -1) {
                /* This should not happen as regexec() did
                * match before, unless there is a
                * discrepency between the regex and the
                * number of backreferences */
                return 0;
            }
            char* str;
            pmatchcpy(&str, clval, &pmatch[pmatch_cnt]);

            stringcpy(element->type, (char*)target_addr + element->offset, str);
            TRACE_CMPD(("setting %p+%zu to : ", target_addr , element->offset));
            print_setting(element->type , (char*)target_addr + element->offset);
            TRACE_CMPD(("\n"));

            /* str is temporary buffer for type conversion, except for strings which we
            * need to keep around so don't free them */
            if (element->type != CFG_STRING)
                free(str);
            pmatch_cnt++;
        } else { /* don't use matching, set constant */
            any_valcpy(element->type, (char*)target_addr + element->offset,
                        target->value);
        }
    }

    return 1;
}

/* Goes over a list, finds if a group matches the specified string and overwrite
* it if it does. */
static int override_on_str(struct compound_cl_arg* arg, const char* str, regmatch_t* pmatch)
{
    struct config_desc * desc = arg->base_entry;
    void* list_base = *(void**)(desc->base_addr + desc->offset);
    size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len);
    size_t elem_size = desc->size;
    int i;

    for (i = 0; i < list_len; i++) {
        char* group_base = ((char*)list_base + i * elem_size);

        char* cfg_member = *(char**)(group_base + arg->override_desc->offset);
        if (!strcmp(str, cfg_member)) {
            memset(group_base, 0, elem_size);
            struct arg_str* arg_cl = *arg->arg_cl;
            if (!set_target_fields(group_base, arg, arg_cl->sval[0], pmatch))
                return 0;
            return 1;
        }
    }
    return 0;
}

/* Goes over a list and override group if needed */
static int override_elem(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch) 
{
    char* str;
    int allocated = 0;
    int res;

    if (arg->override_matchindex) {
        struct arg_str* arg_cl = *arg->arg_cl;
        pmatchcpy(&str, arg_cl->sval[arg_index], &pmatch[arg->override_matchindex]);
        allocated = 1;
    } else {
        str = arg->override_const;
    }

    res = override_on_str(arg, str, pmatch);

    if (allocated) free(str);

    return res;
}

/* Add an argument to a list, overriding if required or
* appending otherwise */
static int add_arg_to_list(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch)
{
    struct config_desc * desc = arg->base_entry;
    void* list_base = *(void**)(desc->base_addr + desc->offset);
    size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len);
    size_t elem_size = desc->size;

    /* are we overriding an existing group? */
    if (arg->override_desc)
        if (override_elem(arg, arg_index, pmatch))
            return 1;

    /* override not found or no override, append element and * zero it out */
    list_len++;
    list_base = realloc(list_base, list_len * elem_size);
    *(size_t*)(desc->base_addr + desc->offset_len) = list_len;
    *(void**)(desc->base_addr + desc->offset) = list_base;
    memset(list_base + (list_len - 1) * elem_size, 0, elem_size);

    struct arg_str* arg_cl = *arg->arg_cl;
    if (!set_target_fields((char*)list_base + (list_len - 1) * elem_size, arg, arg_cl->sval[arg_index], pmatch)) {
        return 0;
    }
    return 1;
}

/* TODO: pass pmatch size as parameter or something */
#define MAX_MATCH 10

#ifndef LIBPCRE
static int regcompmatch_posix( regmatch_t* pmatch, 
                        struct compound_cl_arg* arg, 
                        int arg_index, 
                        char** errmsg)
{
    char* regerr;
    struct arg_str* arg_cl = *arg->arg_cl;
    regex_t preg;
    int res = regcomp(&preg, arg->regex, REG_EXTENDED);
    if (res) {
        int errlen = regerror(res, &preg, NULL, 0);
        regerr = malloc(errlen);
        regerror(res, &preg, regerr, errlen);
        asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
        free(regerr);
        return 0;
    }
    res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0);
    if (res) {
        asprintf(errmsg, "--%s %s: Illegal argument", 
        arg_cl->hdr.longopts, 
        arg->regex); 
        return 0;
    }
    return 1;
}
#endif


#ifdef LIBPCRE
static int regcompmatch_pcre2( regmatch_t* pmatch,
                        struct compound_cl_arg* arg,
                        int arg_index,
                        char** errmsg)
{
    int i, error;
    pcre2_code* pcre;
    PCRE2_UCHAR8 err_str[120];  /* ample, according to pcre2api(3) */
    pcre2_match_data* matches;
    PCRE2_SIZE error_offset;
    struct arg_str* arg_cl = *arg->arg_cl;

    pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);
    if (!pcre) {
        pcre2_get_error_message(error, err_str, sizeof(err_str));
        asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
                arg->regex, error, err_str, error_offset);
        return 0;
    }

    matches = pcre2_match_data_create(MAX_MATCH, NULL);
    int res = pcre2_match(pcre, (PCRE2_SPTR8)arg_cl->sval[arg_index], PCRE2_ZERO_TERMINATED,
                          0, 0, matches, NULL);
    if (res < 0) {
        pcre2_get_error_message(res, err_str, sizeof(err_str));
        asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n", 
                          arg_cl->sval[arg_index], arg->regex, res, err_str);
        return 0;
    }

    PCRE2_SIZE *ovec = pcre2_get_ovector_pointer(matches);
    if (res > MAX_MATCH) res = MAX_MATCH;
    /* From pcre2posix.c */
    for (i = 0; i < res; i++) {
        pmatch[i].rm_so = (ovec[i*2] == PCRE2_UNSET) ? -1 : ovec[i*2];
        pmatch[i].rm_eo = (ovec[i*2+1] == PCRE2_UNSET) ? -1 : ovec[i*2+1];
    }
    for (; i < MAX_MATCH; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1;

    pcre2_match_data_free(matches);
    return 1;
}
#endif


/* Regex fiddling: uses info in arg to fill pmatch
* arg: description of the command line argument
* arg_index: occurence of this argument on the command line
*/
static int regcompmatch(regmatch_t* pmatch,
                        struct compound_cl_arg* arg,
                        int arg_index,
                        char** errmsg)
{
#if LIBPCRE
    return regcompmatch_pcre2(pmatch, arg, arg_index, errmsg);
#else
    return regcompmatch_posix(pmatch, arg, arg_index, errmsg);
#endif
}

/* Read compound options described in `arg`, from `cfg`, to `setting` */
static int read_compounds(config_setting_t* cfg, 
                          void* setting, 
                          struct compound_cl_arg* arg, 
                          char** errmsg)
{
    int arg_i;
    struct arg_str* arg_cl;
    regmatch_t pmatch[MAX_MATCH];

    for (; arg->regex; arg++) {
        arg_cl = *arg->arg_cl;
        TRACE_CMPD(("Compound %s occurs %d : ", arg_cl->hdr.longopts, arg_cl->count));
        for (arg_i = 0; arg_i < arg_cl->count; arg_i++) {
            if (!regcompmatch(&pmatch[0], arg, arg_i, errmsg))
                return 0;
            TRACE_CMPD(("`%s' matched\n", arg_cl->sval[arg_i]));

            switch (arg->base_entry->type) {
            case CFG_LIST:
                /* In a list, find the end or the element to override */
                if (!add_arg_to_list(arg, arg_i, pmatch)) {
                    return 0;
                }
                break;

            /* Semantics for CFG_ARRAY TBD */

            case CFG_GROUP:
                if (!set_target_fields(
                            /* base_addr is the same for all elements in the group */
                            arg->targets[0].element->base_addr, 
                            arg, 
                            arg_cl->sval[arg_i], 
                            pmatch))
                    return 0;

            default:
                TRACE_CMPD(("error, compound on type %d\n", arg->base_entry->type));
                break;
            }
        }
        TRACE_CMPD(("done %s\n", arg_cl->hdr.longopts));
    }
    return 1;
}

/* read config file `filename` into `c` */
static int c2s_parse_file(const char* filename, config_t* c, char**errmsg)
{
    /* Read config file */
    if (config_read_file(c, filename) == CONFIG_FALSE) {
        if (config_error_line(c) != 0) {
           asprintf(errmsg, "%s:%d:%s", 
                    filename,
                    config_error_line(c),
                    config_error_text(c));
           return 0;
        }
        asprintf(errmsg, "%s:%s", filename, config_error_text(c));
        return 0;
    }
    return 1;
}

/* Allocates a new string that represents the setting value, which must be a scalar */
static void scalar_to_string(char** strp, config_setting_t* s)
{
    switch(config_setting_type(s)) {
    case CONFIG_TYPE_INT:
        asprintf(strp, "%d\n", config_setting_get_int(s));
        break;

    case CONFIG_TYPE_BOOL:
        asprintf(strp, "%s\n", config_setting_get_bool(s) ?  "[true]" : "[false]" );
        break;

    case CONFIG_TYPE_INT64:
        asprintf(strp, "%lld\n", config_setting_get_int64(s));
        break;

    case CONFIG_TYPE_FLOAT:
        asprintf(strp, "%lf\n", config_setting_get_float(s));
        break;

    case CONFIG_TYPE_STRING:
        asprintf(strp, "%s\n", config_setting_get_string(s));
        break;

    default: /* This means a bug */
        fprintf(stderr, "Unexpected type %d\n", config_setting_type(s));
        exit(1);
    }
}

/* Typesets all the settings in a configuration as a
* newly-allocated string. The string management is caller's
* responsability. 
* Returns the number of scalars in the configuration */
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
{
    int i, len, res = 0;
    config_setting_t* child;
    char* subpath, *value, *old;
    const char* name;

    len = config_setting_length(parent);
    for (i = 0; i < len; i++) {
        child = config_setting_get_elem(parent, i);
        name = config_setting_name(child);
        if (!name) name = "";

        if(config_setting_is_list(parent) ||
           config_setting_is_array(parent)) {
            asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
        } else {
            asprintf(&subpath, "%s/%s", path, name);
        }

        if (config_setting_is_scalar(child)) {
            scalar_to_string(&value, child);

            /* Add value to the output string  */
            if (*strp) {
                asprintf(&old, "%s", *strp);
                free(*strp);
            }  else {
                asprintf(&old, "%s", "");
            }
            asprintf(strp, "%s%s:%s", old, subpath, value);
            free(value);
            free(old);

            res++; /* At least one scalar was found */
        } else {
            /* It's an aggregate -- descend into it */
            res += cfg_as_string(child, subpath, strp);
        }

        free(subpath);
    }
    return res;
}


/* 0: success
   <0: error */
int ##name_cl_parse(int argc, char* argv[], struct ##name_item* cfg)
{
    int nerrors, res;
    config_t c;
    char* errmsg;
    config_setting_t* s;
    void* argtable[] = {
        ##r_init
    };

    /* Parse command line */
    nerrors = arg_parse(argc, argv, argtable);
    if (nerrors) {
        arg_print_errors(stdout, ##name_end, "##name"); 
        arg_print_syntax(stdout, argtable, "\n");
        arg_print_glossary(stdout, argtable, "  %-25s\t%s\n");
        return -1;
    }


    config_init(&c);
    if (##name_conffile && ##name_conffile->count) {
        if (!c2s_parse_file(##name_conffile->filename[0], &c, &errmsg)) {
            fprintf(stderr, "%s\n", errmsg);
            return -1;
        }
    }

    s = config_root_setting(&c);

    res = read_block(s, cfg, table_##name, &errmsg);
    if (!res) {
        fprintf(stderr, "%s\n", errmsg);
        return -1;
    }

    res = read_compounds(s, cfg, compound_cl_args, &errmsg);
    if (!res) {
        fprintf(stderr, "%s\n", errmsg);
        return -1;
    }

    errmsg = NULL;
    res = cfg_as_string(s, "", &errmsg);
    if (res)
        fprintf(stderr, "Unknown settings:\n%s\n", errmsg);

    return 0;
}

EOF
);


# / C code
################################################################################

################################################################################
# HEADER GENERATION
################################################################################
# Taking a 'setting' (hash with name and @items), returns a
# couple ($structs, $decls) with $structs a string of
# structs needed inside this setting, and $decls the scalar
# definitions for the setting
sub make_decl_list {
    my ($prefix, $setting) = @_;

    my ($structs, $decls) = ("", "");
    foreach my $setting (@{$setting->{items}}) {
        my $name = cfgname2c $setting->{name};
        if ($setting->{type} eq 'list') {
            my $sname = "${prefix}_${name}_item";
            $structs .= make_struct_header("${prefix}_${name}", $setting);
            $decls = "$decls\tsize_t\t${name}_len;\n\tstruct $sname* ${name};\n";
        } elsif ($setting->{type} eq 'group') {
            my $sname = "${prefix}_${name}_item";
            $structs .= make_struct_header("${prefix}_${name}", $setting);
            $decls = "$decls\tstruct $sname* ${name};\n";
        } elsif ($setting->{type} eq 'array') {
            my $type = scalar_type($setting->{element_type});
            $decls= "$decls\tsize_t\t${name}_len;\n\t$type* ${name};\n";
        } elsif ($setting->{type} eq 'runtime') {
            $decls .= "\t$setting->{c_type}\t${name};\n";
        } else {
            my $c_type = scalar_type $setting->{type};
            #if ($setting->{optional} and $setting->{type} eq 'string') {
            if ($setting->{optional}) {
                $decls.= "\tint\t${name}_is_present;\n";
            }
            if ($setting->{var} and $setting->{type} eq 'string') {
                $c_type = "char*";
            }
            $decls.= "\t$c_type\t${name};\n";
        }
    }
    return ($structs, $decls);
}

################################################################################
sub make_struct_header {
    my ($prefix, $setting) = @_;

    my ($s, $l) = make_decl_list($prefix, $setting);
    my $out = "$s\nstruct ${prefix}_item {\n$l};\n";
    return $out;
}

################################################################################
sub make_root_header {
    my ($root) = @_;
    my $setting = $root->{config};
    my $name = cfgname2c $setting->{name};
    my $NAME = uc $name;
    my $out = <<EOF;
$banner 
#ifndef C2S_${NAME}_H
#define C2S_${NAME}_H
#ifdef LIBCONFIG
#    include <libconfig.h>
#endif


EOF

    foreach my $h (@{$root->{includes}}) {
        $h = "\"$h\"" unless $h =~ /\<.*\>/;
        $out .= "#include $h\n";
    }

    $out .= make_struct_header($name, $setting);

    $out .= <<EOF;

int ${name}_parse_file(
        const char* filename,
        struct ${name}_item* $name, 
        const char** errmsg);

void ${name}_fprint(
    FILE* out,
    struct ${name}_item *${name},
    int depth);

int ${name}_cl_parse(
    int argc,
    char* argv[],
    struct ${name}_item *${name});

#endif
EOF

    return $out;
}

################################################################################
# PARSER GENERATION
################################################################################
# opts:
#       no_cl: disable command line options for this group
#       and groups further below
sub make_table {
    my ($name, $top_setting, %opts) = @_;
    my @settings = @{$top_setting->{items}};

    my $out = <<EOF;

static struct config_desc table_${name}[] = {

EOF

    foreach my $setting (@settings) {
        next if $setting->{type} eq 'runtime';

        my $sname = cfgname2c $setting->{name};
        my $hname = "${name}_${sname}";
        my $cfg_type = uc "CFG_$setting->{type}";
        my $optional = $setting->{optional} // "0";
        my $default = $setting->{default};
        my $mandatory = (not ($optional or defined $default)) ? 1 : 0;
        if ($setting->{type} eq 'string') {
            $default = defined $default ? "\"$default\"" : "NULL";
        } else {
            $default = $default // 0;
        }
        $default = ".default_val.def_$setting->{type} = $default";
        my $offset_present = $setting->{optional} ? "offsetof(struct ${name}_item, ${sname}_is_present)" : 0;
        my $c = "";
        my $sub_group = "NULL";
        my $offset_len = 0;
        my $array_type = -1;

        my $arg_cl = "NULL"; 

        my $sizeof;
        if ($setting->{type} =~ /list|group/) {
            my $no_cl = $opts{no_cl} || $setting->{no_cl_accessors};
            $c = make_table($hname, $setting, no_cl => $no_cl);
            $sub_group = "table_${hname}";
            $sizeof = "struct ${hname}_item";
            $default = ".default_val.def_int = 0";
            $arg_cl = "NULL";
        }
        if (is_scalar $setting) {
            $sizeof = scalar_type $setting->{type};
            $arg_cl = "& ${name}_${sname}" unless $opts{no_cl};
        }
        if ($setting->{type} =~ /list/) {
            $offset_len = "offsetof(struct ${name}_item, ${sname}_len)";
        }

        if ($setting->{type} =~ /array/) {
            $offset_len = "offsetof(struct ${name}_item, ${sname}_len)";
            $sizeof = scalar_type $setting->{element_type};
            $default = $setting->{default} // 0;
            $default = ".default_val.def_int = $default";
            $array_type = uc "CFG_$setting->{element_type}";
        }

        $out = <<EOF;
        $c $out
        { 
            /* name */          "$sname", 
            /* type */          $cfg_type, 
            /* sub_group*/      $sub_group,
            /* arg_cl */        $arg_cl,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct ${name}_item, $sname),
            /* offset_len */    $offset_len,
            /* offset_present */ $offset_present,
            /* size */          sizeof($sizeof), 
            /* array_type */    $array_type,
            /* mandatory */     $mandatory, 
            /* optional */      $optional, 
            /* default_val*/    $default 
        },
EOF

    }
    $out .= "\t{ 0 }\n};\n";

    return $out;
}

# search for an item in an array for which the field matches $name
# If $field_name is not precised, search for 'name'.
# Returns the index in the array.
# E.g.:
# @a = ({ name => 'joe', id => 34 }, { name => 'jack', id => 456 });
# search_name_index(\@a, 'jack'); # returns 1
# search_name_index(\@a, 34, 'id'); # returns 0
sub search_name_index {
    my ($item, $name, $field_name) = @_;

    $field_name //= 'name';

    my $i = 0;
    until ($item->[$i]->{$field_name} eq $name) {
        $i++;
        if (not defined $item->[$i]) { 
            warn "$name not found\n" ; 
            return ;
        }
    }
    return $i;
}

# Creates a pointer to the C descriptor that hosts schema element $s
# $name: basename of the config
# $root: root of the schema
# $s: element to find
# returns e.g. " & table_eg[5] ": $s is the 5th element in that table
sub make_baseref {
    my ($name, $root, $s) = @_;
    my $typename = cfgname2c $s->{list};
    my $base_entry = "table_${name}_${typename}";
    $base_entry =~ s/_[a-zA-Z0-9]+?$//;
    my $be = $base_entry;
    $be =~ s/_/./g;
    $be =~ s/^\w+?\.//;
    $be =~ s/^\w+?\.//;
    $be = "" if $be eq $root->{config}->{name};
    my $it = get_item($root, $be);
    my $leaf_name = $s->{list};
    $leaf_name =~ s/^.*\.//;
    return "& $base_entry [". search_name_index($it->{items}, $leaf_name). "]";
}

################################################################################
# Make a cstruct name from a command line setting name;
# Essentially, this is prefix + setting name, with dashes
# turned to underscore:
# make_cstruct_name "eg", "list-edit" => "eg_list_edit"
sub make_cstruct_name {
    my ($prefix, $name) = @_;
    $name =~ s/-/_/g;
    return "${prefix}_$name";
}


################################################################################
# Generate table for compound option processing
sub make_compound_table {
    my ($name, $root) = @_;

    my $out = <<EOF;
static struct compound_cl_arg compound_cl_args[] = {
EOF

    foreach my $s (@{$root->{cl_groups}}) {
        my $argname = $s->{name};

        my $typename = cfgname2c $s->{list};
        my $structname = $s->{list};
        $structname =~ s/\./->/g;

        my $cstruct_basename = make_cstruct_name($name, $s->{name});
        my $list_targets = "${cstruct_basename}_targets";
        my $entries = "static struct compound_cl_target $list_targets [] = {\n";
        my $item = get_item($root, $s->{list});

        foreach my $t (@{$s->{targets}}) {
            my $index = search_name_index($item->{items}, $t->{path});
            my $match = ($t->{value} =~ /\$(\d+)/) ? "$1": 0;
            my $value = $match ? 0 : "$t->{value}";
            my $type = $item->{items}[$index]->{type};
            $value = "\"$value\"" if $type eq 'string';
            $value = ".value.def_$type = $value";

            $entries .= "\t{ & table_${name}_${typename}[$index], $match, $value },\n";
        }
        $entries .= "\t{ 0 }\n};\n\n";

        $out = "$entries$out";

        my $targets = "$list_targets";
        my $arg_cl = "& $cstruct_basename";
        my $regex = $s->{pattern};
        $regex =~ s/\\/\\\\/g; # C interprets \d as escape sequence, so escape backslashes

        my $base_entry = make_baseref($name, $root, $s);

        # TODO: Override only works on groups, reject it otherwise
        my ($override_desc, $override_const, $override_index);
        $override_desc = "NULL";
        $override_index = 0;
        $override_const = "NULL";
        if ($s->{override}) {
            $override_desc = "& table_${name}_$typename [".
                search_name_index($item->{items}, $s->{override})."]";
            # Find override entry in targets
            my $i = search_name_index($s->{targets}, $s->{override}, 'path');
            my $e = $s->{targets}->[$i];
            my ($path, $value) = ($e->{path}, $e->{value});
            if ($value =~ /^\$(\d+)$/) {
                $override_index = $1;
            } else {
                $override_const = "\"$value\"";
            }
        }


        $out .= <<EOF;
        {   /* arg: $argname */
            .regex =           "$regex",
            .arg_cl =          $arg_cl,
            .base_entry =      $base_entry,
            .targets =         $targets,


            .override_desc =   $override_desc,
            .override_matchindex = $override_index,
            .override_const = $override_const,
        },

EOF
    }
    $out .= "\t{ 0 }\n};\n\n";

    return $out;
}

################################################################################
sub make_c_head {
    return <<EOF;
$banner
#define _GNU_SOURCE
#include <string.h>
#ifdef LIBCONFIG
#    include <libconfig.h>
#endif
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include "$header"
#include "argtable3.h"
#ifdef LIBPCRE
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
typedef struct {
    PCRE2_SIZE rm_so;
    PCRE2_SIZE rm_eo;
} regmatch_t;
#else
#include <regex.h>
#endif

EOF
}

################################################################################
sub make_root_parser {
    my ($root) = @_;
    my $setting = $root->{config};
    my $name = cfgname2c $setting->{name};

    my $out = $snippets{header};
    my ($r_def, $r_init) = make_argtable_declarations($name, 1, $setting);
    my ($r_def2, $r_init2) = make_argtable_cl_groups_decls($name, @{$root->{cl_groups}});
    push @$r_def, @$r_def2;
    push @$r_init, @$r_init2;

    push @$r_def, "struct arg_end* ${name}_end;\n";
    push @$r_init, "\t${name}_end = arg_end(10)\n";

    # Add configuration file settings
    die "conffile_option not set\n" unless defined $root->{conffile_option};
    my ($short_opt, $long_opt) = @{$root->{conffile_option}};
    unshift @$r_def, "struct arg_file* ${name}_conffile;\n";
    unshift @$r_init, <<EOF;
    #ifdef LIBCONFIG
        ${name}_conffile = arg_filen("$short_opt", "$long_opt", "<file>", 0, 1, "Specify configuration file"),
    #endif
EOF

    
    $out .= <<EOF;
@$r_def
EOF

    $out .= make_table($name, $setting);

    $out .= make_compound_table($name, $root);

    $out .= $snippets{c_read_block};

    # Interpolate variables
    my %vars = (
        r_init => "@$r_init",
        name => "$name",
    );
    while (my ($vname, $vval) = each %vars) {
        $out =~ s/##$vname/$vval/g;
    }

    return $out;
}

################################################################################
# PRINTER GENERATION
################################################################################
# This is generated code instead of table-based, so it
# serves as an example of how to use the resulting C struct
# Usually projects won't need to include this code
sub make_print_settings {
    my ($name, $top_setting) = @_;
    my @settings = @{$top_setting->{items}};

    my $out = <<EOF;
static void ${name}_fprint(
        FILE* out,
        struct ${name}_item* $name,
        int depth) 
{
    ITERATOR_PLACEHOLDER
EOF

    my $need_iterator;
    foreach my $setting (@settings) {
        my $sname = cfgname2c $setting->{name};
        if ($setting->{type} eq 'list') {
            my $hname = "${name}_${sname}";
            my $pname = "${hname}_fprint";
            my $c = make_print_settings($hname, $setting);
            $need_iterator = 1;
            $out = <<EOF
$c
$out
        indent(out, depth);
        fprintf(out, "${sname} [%zu]:\\n", $name->${sname}_len);
        for (i = 0; i < $name->${sname}_len; i++) {
            $pname(out, &$name->${sname}\[i\], depth+1);
        }
EOF
        } elsif ($setting->{type} eq 'group') {
            my $hname = "${name}_${sname}";
            my $pname = "${hname}_fprint";
            my $c = make_print_settings($hname, $setting);
            $out = <<EOF
$c
$out
        indent(out, depth);
        fprintf(out, "${sname}:\\n");
        $pname(out, $name->${sname}, depth+1);
EOF

        } elsif ($setting->{type} eq 'array') {
            my $fmt = scalar_type_print $setting->{element_type};
            $need_iterator = 1;
            $out .= <<EOF
        indent(out, depth);
        fprintf(out, "${sname} [%zu]:\\n", $name->${sname}_len);
        for (i = 0; i < $name->${sname}_len; i++) {
            indent(out, depth+1);
            fprintf(out, "%d:\\t$fmt\\n", i, $name->${sname}\[i\]);
        }
EOF
        } elsif ($setting->{type} eq 'runtime') {
            # Don't print it, we know nothing about it
        } else {
            my $fmt = scalar_type_print $setting->{type};
            $out .= <<EOF;
        indent(out, depth);
        fprintf(out, "${sname}: $fmt", $name->${sname});
EOF
            if ($setting->{optional}) {
                $out .= <<EOF
        if (! $name->${sname}_is_present)
            fprintf(out, " <unset>");
EOF
            }
            $out .= <<EOF;
        fprintf(out, "\\n");
EOF
        }
    }

    if ($need_iterator) {
        $out =~ s/ITERATOR_PLACEHOLDER/int i;/;
    } else {
        $out =~ s/ITERATOR_PLACEHOLDER//;
    }

    $out .= "}\n";

    return $out;
}


################################################################################
sub make_root_print_settings {
    my ($root) = @_;
    my $setting = $root->{config};
    my $name = cfgname2c $setting->{name};

    my $out = <<EOF;

static void indent(FILE* out, int depth) 
{
    int i;
    for (i = 0; i < depth; i++)
        fprintf(out, "    ");
}

EOF

    $out .= make_print_settings($name, $setting);

    # Make the root printer a public symbol
    $out =~ s/static void ${name}_fprint/void ${name}_fprint/;
    return $out;
}

################################################################################

sub argtype {
    my ($in) = @_;
    my $out = {
        'bool' => 'arg_lit',
        'int' => 'arg_int',
        'int64' => 'arg_int',
        'float' => 'arg_dbl',
        'string' => 'arg_str',
    }->{$in};
    return $out;
}

# Type name inside the argtable structure
sub structtype {
    my ($in) = @_;
    my $out = {
        'bool' => 'count',
        'int' => 'ival',
        'int64' => 'ival',
        'float' => 'dval',
        'string' => 'sval',
    }->{$in};
    return $out;
}

################################################################################
sub make_argtable_declarations {
    my ($prefix, $max, $in) = @_;

    my (@def, @init);
    foreach my $setting (@{$in->{items}}) {
        next if $setting->{type} =~ /runtime/;

        $setting->{description} //= "";
        my $arg_type = argtype( $setting->{type} eq 'array'?  
            $setting->{element_type} : 
            $setting->{type});
        my $name = "${prefix}_" . cfgname2c $setting->{name};
        if ($setting->{type} =~ /group|list/) {
            next if exists $setting->{no_cl_accessors};
            my $sname = cfgname2c $setting->{name};
            my ($r_def, $r_init) = make_argtable_declarations("${prefix}_$sname", 10, $setting);
            push @def, @{$r_def};
            push @init, @{$r_init};
        } else {
            push @def, "struct $arg_type* $name;\n";
            my $arg_name = $name;
            $arg_name =~ s/.*?_//;  # Remove first prefix from cmdline name
            $arg_name =~ s/_/-/g;  # Command-line arg uses dashes
            $max = 10 if $setting->{type} eq 'array';
            my $type = ", NULL";
            $type = "" if $arg_type eq 'arg_lit';
            $type = ", \"<n>\"" if $arg_type =~ /arg_int|arg_dbl/;
            $type = ", \"<str>\"" if $arg_type eq 'arg_str';
            $type = ", \"$setting->{argdesc}\"" if exists $setting->{argdesc};
            my $short = "NULL";
            $short = "\"$setting->{short}\"" if exists $setting->{short};
            push @init, <<EOF;
        $name = ${arg_type}n($short, "$arg_name"$type, 0, $max, "$setting->{description}"),
EOF
        }
    }
    return (\@def, \@init);
}

################################################################################
# Create declarations for group command line options
sub make_argtable_cl_groups_decls {
    my ($prefix, @groups) = @_;

    my (@def, @init);
    foreach my $g (@groups) {
        my $type = "NULL";
        $type = "\"$g->{argdesc}\"" if exists $g->{argdesc};
        my $short = "NULL";
        $short = "\"$g->{short}\"" if exists $g->{short};
        my $name = make_cstruct_name $prefix, $g->{name};
        $g->{description} //= "";
        push @def, "\tstruct arg_str* $name;\n";
        push @init, "\t$name = arg_strn($short, \"$g->{name}\", $type, 0, 10, \"$g->{description}\"),\n";
    }
    return (\@def, \@init);
}

# Returns an item given a configuration object and a
# libconfig path, e.g. "application.window.size"
sub get_item {
    my ($root, $path) = @_;
    my $e = $root->{config};
    my @p = split /\./, $path;
    foreach my $p (@p) {
        $e = (grep { $_->{name} eq $p } @{$e->{items}})[0];
    }
    return $e;
}

################################################################################
# main
################################################################################

open my $h, "> $header" or die "$header: $!\n";
print $h make_root_header($hr);

open my $c, "> $parser" or die "$parser: $!\n";
print $c make_c_head;
print $c make_root_parser($hr);
print $c make_root_print_settings($hr) if $printer;


