The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* vim:set ts=4 sw=4 et syntax=c.doxygen: */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include "convert.h"

SV *
_convert_recurse(const ps_node *node, int flags, const char *prefix)
{
    SV *result = &PL_sv_undef;

    const union nodeval *v = &node->val;
    char *typename = NULL;
    const struct array *what = NULL;
    SV *a = NULL;
    switch (node->type) {
        case NODE_STRING: result = newSVpv(v->s.val, v->s.len);            break;
        case NODE_INT:    result = newSViv(v->i);                          break;
        case NODE_FLOAT:  result = newSVnv(v->d);                          break;
        case NODE_BOOL:   result = newSVsv(v->b ? &PL_sv_yes : &PL_sv_no); break;
        case NODE_NULL:   result = newSVsv(&PL_sv_undef);                  break;
        case NODE_OBJECT:
            what = &node->val.o.val;
            typename = v->o.type;
            goto inside_array;
        case NODE_ARRAY: {
            what = &node->val.a;
        inside_array:
            // len == 0 could be hash still
            if (flags & PS_XS_PREFER_HASH || !what->is_array) {
                a = (SV*)newHV();
                for (int i = 0; i < what->len; i++) {
                    int len;
                    char *key = SvPV(_convert_recurse(what->pairs[i].key, flags, prefix), len);
                    SV   *val =      _convert_recurse(what->pairs[i].val, flags, prefix);

                    hv_store((HV*)a, key, len, val, 0);
                }
            } else {
                a = (SV*)newAV();
                av_extend((AV*)a, what->len - 1);
                for (int i = 0; i < what->len; i++)
                    av_push((AV*)a, _convert_recurse(what->pairs[i].val, flags, prefix));
            }

            result = newRV(a);
            if (typename) {
                bool should_free = false;
                char *built = typename;
                if (prefix) {
                    should_free = true;
                    size_t size = snprintf(NULL, 0, "%s::%s", prefix, typename);
                    built = malloc(size + 1);
                    snprintf(built, size + 1, "%s::%s", prefix, typename);
                }

                sv_bless(result, gv_stashpv(built, true));

                if (should_free)
                    free(built);
            }

            break;
        }
    }

    return result;
}