/* -*- c++ -*- */
/*
 * Copyright 2007,2009 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "pmt/pmt_serial_tags.h"
#include "pmt_int.h"
#include <pmt/pmt.h>
#include <boost/endian/conversion.hpp>
#include <limits>
#include <vector>

namespace pmt {

static pmt_t parse_pair(std::streambuf& sb, uint8_t type);

// ----------------------------------------------------------------
// output primitives
// ----------------------------------------------------------------

static bool serialize_untagged_u8(uint8_t i, std::streambuf& sb)
{
    return sb.sputc((i >> 0) & 0xff) != std::streambuf::traits_type::eof();
}

// always writes big-endian
static bool serialize_untagged_u16(uint16_t i, std::streambuf& sb)
{
    boost::endian::native_to_big_inplace(i);
    return sb.sputn((char*)&i, sizeof(i)) != std::streambuf::traits_type::eof();
}

// always writes big-endian
static bool serialize_untagged_u32(uint32_t i, std::streambuf& sb)
{
    boost::endian::native_to_big_inplace(i);
    return sb.sputn((char*)&i, sizeof(i)) != std::streambuf::traits_type::eof();
}

static bool serialize_untagged_f64(double i, std::streambuf& sb)
{
    typedef union {
        double id;
        uint64_t ii;
    } iu_t;
    iu_t iu;
    iu.id = i;
    boost::endian::native_to_big_inplace(iu.ii);
    return sb.sputn((char*)&iu.ii, sizeof(iu.ii)) != std::streambuf::traits_type::eof();
}

// always writes big-endian
static bool serialize_untagged_u64(uint64_t i, std::streambuf& sb)
{
    boost::endian::native_to_big_inplace(i);
    return sb.sputn((char*)&i, sizeof(i)) != std::streambuf::traits_type::eof();
}

// always writes big-endian
static bool
serialize_untagged_u8_array(const uint8_t* data, size_t length, std::streambuf& sb)
{
    return sb.sputn((char*)data, length) != std::streambuf::traits_type::eof();
}

static bool
serialize_untagged_u16_array(const uint16_t* data, size_t length, std::streambuf& sb)
{
    std::vector<uint16_t> bedata(length);
    for (size_t i = 0; i < length; i++) {
        bedata[i] = boost::endian::native_to_big(data[i]);
    }
    return sb.sputn((char*)&bedata[0], length * sizeof(uint16_t)) !=
           std::streambuf::traits_type::eof();
}

static bool
serialize_untagged_u32_array(const uint32_t* data, size_t length, std::streambuf& sb)
{
    std::vector<uint32_t> bedata(length);
    for (size_t i = 0; i < length; i++) {
        bedata[i] = boost::endian::native_to_big(data[i]);
    }
    return sb.sputn((char*)&bedata[0], length * sizeof(uint32_t)) !=
           std::streambuf::traits_type::eof();
}

static bool
serialize_untagged_u64_array(const uint64_t* data, size_t length, std::streambuf& sb)
{
    std::vector<uint64_t> bedata(length);
    for (size_t i = 0; i < length; i++) {
        bedata[i] = boost::endian::native_to_big(data[i]);
    }
    return sb.sputn((char*)&bedata[0], length * sizeof(uint64_t)) !=
           std::streambuf::traits_type::eof();
}

// ----------------------------------------------------------------
// input primitives
// ----------------------------------------------------------------

// always reads big-endian
static bool deserialize_untagged_u8(uint8_t* ip, std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;
    int i;

    t = sb.sbumpc();
    i = t & 0xff;

    *ip = i;
    return t != std::streambuf::traits_type::eof();
}

// always reads big-endian
static bool deserialize_untagged_u16(uint16_t* ip, std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;
    t = sb.sgetn((char*)ip, sizeof(uint16_t));
    sb.pubseekoff(sizeof(uint16_t), std::ios_base::cur);
    boost::endian::big_to_native_inplace(*ip);

    return t != std::streambuf::traits_type::eof();
}

// always reads big-endian
static bool deserialize_untagged_u32(uint32_t* ip, std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;
    t = sb.sgetn((char*)ip, sizeof(uint32_t));
    sb.pubseekoff(sizeof(uint32_t), std::ios_base::cur);
    boost::endian::big_to_native_inplace(*ip);

    return t != std::streambuf::traits_type::eof();
}

// always reads big-endian
static bool deserialize_untagged_u64(uint64_t* ip, std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;
    t = sb.sgetn((char*)ip, sizeof(uint64_t));
    sb.pubseekoff(sizeof(uint64_t), std::ios_base::cur);
    boost::endian::big_to_native_inplace(*ip);

    return t != std::streambuf::traits_type::eof();
}

static bool deserialize_untagged_f64(double* ip, std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;

    typedef union {
        double id;
        uint64_t ii;
    } iu_t;

    iu_t iu;
    t = sb.sgetn((char*)&iu, sizeof(uint64_t));
    sb.pubseekoff(sizeof(uint64_t), std::ios_base::cur);
    boost::endian::big_to_native_inplace(iu.ii);
    *ip = iu.id;

    return t != std::streambuf::traits_type::eof();
}

static bool deserialize_tuple(pmt_t* tuple, std::streambuf& sb)
{
    uint32_t nitems;
    bool ok = deserialize_untagged_u32(&nitems, sb);
    pmt_t list(PMT_NIL);
    for (uint32_t i = 0; i < nitems; i++) {
        pmt_t item = deserialize(sb);
        if (eq(list, PMT_NIL)) {
            list = list1(item);
        } else {
            list = list_add(list, item);
        }
    }
    (*tuple) = to_tuple(list);
    return ok;
}

// always reads big-endian
static bool deserialize_untagged_u8_vector(std::vector<uint8_t>& data,
                                           size_t nitems,
                                           std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;
    data.resize(nitems);
    t = sb.sgetn((char*)&data[0], nitems);
    sb.pubseekoff(nitems, std::ios_base::cur);

    return t != std::streambuf::traits_type::eof();
}

static bool deserialize_untagged_u16_vector(std::vector<uint16_t>& data,
                                            size_t nitems,
                                            std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;
    data.resize(nitems);
    t = sb.sgetn((char*)&data[0], nitems * sizeof(uint16_t));
    sb.pubseekoff(sizeof(uint16_t) * nitems, std::ios_base::cur);
    for (size_t i = 0; i < nitems; i++) {
        boost::endian::big_to_native_inplace(data[i]);
    }

    return t != std::streambuf::traits_type::eof();
}

static bool deserialize_untagged_u32_vector(std::vector<uint32_t>& data,
                                            size_t nitems,
                                            std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;
    data.resize(nitems);
    t = sb.sgetn((char*)&data[0], nitems * sizeof(uint32_t));
    sb.pubseekoff(sizeof(uint32_t) * nitems, std::ios_base::cur);
    for (size_t i = 0; i < nitems; i++) {
        boost::endian::big_to_native_inplace(data[i]);
    }

    return t != std::streambuf::traits_type::eof();
}

static bool deserialize_untagged_u64_vector(std::vector<uint64_t>& data,
                                            size_t nitems,
                                            std::streambuf& sb)
{
    std::streambuf::traits_type::int_type t;
    data.resize(nitems);
    t = sb.sgetn((char*)&data[0], nitems * sizeof(uint64_t));
    sb.pubseekoff(sizeof(uint64_t) * nitems, std::ios_base::cur);
    for (size_t i = 0; i < nitems; i++) {
        boost::endian::big_to_native_inplace(data[i]);
    }

    return t != std::streambuf::traits_type::eof();
}

/*
 * Write portable byte-serial representation of \p obj to \p sb
 *
 * N.B., Circular structures cause infinite recursion.
 */
bool serialize(pmt_t obj, std::streambuf& sb)
{
    bool ok = true;

tail_recursion:

    if (is_bool(obj)) {
        if (eq(obj, PMT_T))
            return serialize_untagged_u8(PST_TRUE, sb);
        else
            return serialize_untagged_u8(PST_FALSE, sb);
    }

    if (is_null(obj))
        return serialize_untagged_u8(PST_NULL, sb);

    if (is_symbol(obj)) {
        const std::string s = symbol_to_string(obj);
        size_t len = s.size();
        ok = serialize_untagged_u8(PST_SYMBOL, sb);
        ok &= serialize_untagged_u16(len, sb);
        ok &= serialize_untagged_u8_array((const uint8_t*)s.c_str(), len, sb);
        return ok;
    }

    if (is_pair(obj)) {
        ok = serialize_untagged_u8(is_dict(obj) ? PST_DICT : PST_PAIR, sb);
        ok &= serialize(car(obj), sb);
        if (!ok)
            return false;
        obj = cdr(obj);
        goto tail_recursion;
    }

    if (is_number(obj)) {

        if (is_uint64(obj)) {
            uint64_t i = to_uint64(obj);
            ok = serialize_untagged_u8(PST_UINT64, sb);
            ok &= serialize_untagged_u64(i, sb);
            return ok;
        } else {
            if (is_integer(obj)) {
                long i = to_long(obj);
                if ((sizeof(long) > 4) &&
                    ((i < std::numeric_limits<std::int32_t>::min() ||
                      i > std::numeric_limits<std::int32_t>::max()))) {
                    // Serializing as 4 bytes won't work for this value, serialize as 8
                    // bytes
                    ok = serialize_untagged_u8(PST_INT64, sb);
                    ok &= serialize_untagged_u64(i, sb);
                } else {
                    ok = serialize_untagged_u8(PST_INT32, sb);
                    ok &= serialize_untagged_u32(i, sb);
                }
                return ok;
            }
        }

        if (is_real(obj)) {
            double i = to_double(obj);
            ok = serialize_untagged_u8(PST_DOUBLE, sb);
            ok &= serialize_untagged_f64(i, sb);
            return ok;
        }

        if (is_complex(obj)) {
            std::complex<double> i = to_complex(obj);
            ok = serialize_untagged_u8(PST_COMPLEX, sb);
            ok &= serialize_untagged_f64(i.real(), sb);
            ok &= serialize_untagged_f64(i.imag(), sb);
            return ok;
        }
    }

    if (is_vector(obj)) {
        size_t vec_len = pmt::length(obj);
        ok = serialize_untagged_u8(PST_VECTOR, sb);
        ok &= serialize_untagged_u32(vec_len, sb);
        for (size_t i = 0; i < vec_len; i++) {
            ok &= serialize(vector_ref(obj, i), sb);
        }
        return ok;
    }

    if (is_uniform_vector(obj)) {
        size_t npad = 1;
        size_t vec_len = pmt::length(obj);

        if (is_u8vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_U8, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u8_array(&u8vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_s8vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_S8, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u8_array(
                (uint8_t*)&s8vector_elements(obj)[0], vec_len, sb);

            return ok;
        }

        if (is_u16vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_U16, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u16_array(&u16vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_s16vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_S16, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u16_array(
                (uint16_t*)&s16vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_u32vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_U32, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u32_array(&u32vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_s32vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_S32, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u32_array(
                (uint32_t*)&s32vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_u64vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_U64, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u64_array(&u64vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_s64vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_S64, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u64_array(
                (uint64_t*)&s64vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_f32vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_F32, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u32_array(
                (uint32_t*)&f32vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_f64vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_F64, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            ok &= serialize_untagged_u64_array(
                (uint64_t*)&f64vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_c32vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_C32, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            // Note that if endianness causes byte swap that real/imag will also be
            // swapped
            ok &= serialize_untagged_u64_array(
                (uint64_t*)&c32vector_elements(obj)[0], vec_len, sb);
            return ok;
        }

        if (is_c64vector(obj)) {
            ok = serialize_untagged_u8(PST_UNIFORM_VECTOR, sb);
            ok &= serialize_untagged_u8(UVI_C64, sb);
            ok &= serialize_untagged_u32(vec_len, sb);
            ok &= serialize_untagged_u8(npad, sb);
            for (size_t i = 0; i < npad; i++) {
                ok &= serialize_untagged_u8(0, sb);
            }
            // No known portable 128 bit swap function, so double the length
            ok &= serialize_untagged_u64_array(
                (uint64_t*)&c64vector_elements(obj)[0], vec_len * 2, sb);
            return ok;
        }
    }

    if (is_dict(obj))
        throw notimplemented("pmt::serialize (dict)", obj);

    if (is_tuple(obj)) {
        size_t tuple_len = pmt::length(obj);
        ok = serialize_untagged_u8(PST_TUPLE, sb);
        ok &= serialize_untagged_u32(tuple_len, sb);
        for (size_t i = 0; i < tuple_len; i++) {
            ok &= serialize(tuple_ref(obj, i), sb);
        }
        return ok;
    }
    // throw pmt_notimplemented("pmt::serialize (tuple)", obj);

    throw notimplemented("pmt::serialize (?)", obj);
}

/*
 * Create obj from portable byte-serial representation
 *
 * Returns next obj from streambuf, or PMT_EOF at end of file.
 * Throws exception on malformed input.
 */
pmt_t deserialize(std::streambuf& sb)
{
    uint8_t tag;
    uint8_t u8;
    uint16_t u16;
    uint32_t u32;
    uint64_t u64;
    double f64;
    char tmpbuf[1024];

    if (!deserialize_untagged_u8(&tag, sb))
        return PMT_EOF;

    switch (tag) {
    case PST_TRUE:
        return PMT_T;

    case PST_FALSE:
        return PMT_F;

    case PST_NULL:
        return PMT_NIL;

    case PST_SYMBOL:
        if (!deserialize_untagged_u16(&u16, sb))
            goto error;
        if (u16 > sizeof(tmpbuf))
            throw notimplemented("pmt::deserialize: very long symbol", PMT_F);
        if (sb.sgetn(tmpbuf, u16) != u16)
            goto error;
        return intern(std::string(tmpbuf, u16));

    case PST_INT32:
        if (!deserialize_untagged_u32(&u32, sb))
            goto error;
        return from_long((int32_t)u32);

    case PST_UINT64:
        if (!deserialize_untagged_u64(&u64, sb))
            goto error;
        return from_uint64(u64);

    case PST_INT64:
        if (!deserialize_untagged_u64(&u64, sb))
            goto error;
        return from_long(u64);

    case PST_PAIR:
        return parse_pair(sb, PST_PAIR);

    case PST_DICT:
        return parse_pair(sb, PST_DICT);

    case PST_DOUBLE:
        if (!deserialize_untagged_f64(&f64, sb))
            goto error;
        return from_double(f64);

    case PST_COMPLEX: {
        double r, i;
        if (!deserialize_untagged_f64(&r, sb) || !deserialize_untagged_f64(&i, sb))
            goto error;
        return make_rectangular(r, i);
    }

    case PST_TUPLE: {
        pmt_t tuple;
        if (!deserialize_tuple(&tuple, sb)) {
            goto error;
        }
        return tuple;
    }

    case PST_VECTOR: {
        uint32_t nitems;
        if (!deserialize_untagged_u32(&nitems, sb))
            goto error;
        pmt_t vec = make_vector(nitems, PMT_NIL);
        for (uint32_t i = 0; i < nitems; i++) {
            pmt_t item = deserialize(sb);
            vector_set(vec, i, item);
        }
        return vec;
    }

    case PST_UNIFORM_VECTOR: {
        uint8_t utag, npad;
        uint32_t nitems;
        std::vector<uint8_t> u8v;
        std::vector<uint16_t> u16v;
        std::vector<uint32_t> u32v;
        std::vector<uint64_t> u64v;

        if (!deserialize_untagged_u8(&utag, sb))
            return PMT_EOF;

        if (!deserialize_untagged_u32(&nitems, sb))
            goto error;

        deserialize_untagged_u8(&npad, sb);
        for (size_t i = 0; i < npad; i++)
            deserialize_untagged_u8(&u8, sb);


        switch (utag) {
        case (UVI_U8): {
            deserialize_untagged_u8_vector(u8v, nitems, sb);
            pmt_t vec = init_u8vector(nitems, &u8v[0]);
            return vec;
        }
        case (UVI_S8): {
            deserialize_untagged_u8_vector(u8v, nitems, sb);
            pmt_t vec = init_s8vector(nitems, (int8_t*)&u8v[0]);
            return vec;
        }
        case (UVI_U16): {
            deserialize_untagged_u16_vector(u16v, nitems, sb);
            pmt_t vec = init_u16vector(nitems, &u16v[0]);
            return vec;
        }
        case (UVI_S16): {
            deserialize_untagged_u16_vector(u16v, nitems, sb);
            pmt_t vec = init_s16vector(nitems, (int16_t*)&u16v[0]);
            return vec;
        }
        case (UVI_U32): {
            deserialize_untagged_u32_vector(u32v, nitems, sb);
            pmt_t vec = init_u32vector(nitems, &u32v[0]);
            return vec;
        }
        case (UVI_S32): {
            deserialize_untagged_u32_vector(u32v, nitems, sb);
            pmt_t vec = init_s32vector(nitems, (int32_t*)&u32v[0]);
            return vec;
        }
        case (UVI_U64): {
            deserialize_untagged_u64_vector(u64v, nitems, sb);
            pmt_t vec = init_u64vector(nitems, &u64v[0]);
            return vec;
        }
        case (UVI_S64): {
            deserialize_untagged_u64_vector(u64v, nitems, sb);
            pmt_t vec = init_s64vector(nitems, (int64_t*)&u64v[0]);
            return vec;
        }
        case (UVI_F32): {
            deserialize_untagged_u32_vector(u32v, nitems, sb);
            pmt_t vec = init_f32vector(nitems, (float*)&u32v[0]);
            return vec;
        }
        case (UVI_F64): {
            deserialize_untagged_u64_vector(u64v, nitems, sb);
            pmt_t vec = init_f64vector(nitems, (double*)&u64v[0]);
            return vec;
        }
        case (UVI_C32): {
            // Data was serialized as uint64, so do the same here
            deserialize_untagged_u64_vector(u64v, nitems, sb);
            pmt_t vec = init_c32vector(nitems, (std::complex<float>*)&u64v[0]);
            return vec;
        }

        case (UVI_C64): {
            // Do 64 bits at a time, so we need to double length
            deserialize_untagged_u64_vector(u64v, 2 * nitems, sb);
            pmt_t vec = init_c64vector(nitems, (std::complex<double>*)&u64v[0]);
            return vec;
        }

        default:
            throw exception("pmt::deserialize: malformed input stream, tag value = ",
                            from_long(tag));
        }
    }

    case PST_COMMENT:
        throw notimplemented("pmt::deserialize: tag value = ", from_long(tag));

    default:
        throw exception("pmt::deserialize: malformed input stream, tag value = ",
                        from_long(tag));
    }

error:
    throw exception("pmt::deserialize: malformed input stream", PMT_F);
}

/*
 * provide a simple string accessor to the serialized pmt form
 */
std::string serialize_str(pmt_t obj)
{
    std::stringbuf sb;
    serialize(obj, sb);
    return sb.str();
}

/*
 * provide a simple string accessor to the deserialized pmt form
 */
pmt_t deserialize_str(std::string s)
{
    std::stringbuf sb(s);
    return deserialize(sb);
}

/*
 * This is a mostly non-recursive implementation that allows us to
 * deserialize very long lists w/o exhausting the evaluation stack.
 *
 * On entry we've already eaten the PST_PAIR or PST_DICT tag.
 */
pmt_t parse_pair(std::streambuf& sb, uint8_t type)
{
    uint8_t tag;
    pmt_t val, expr, lastnptr, nptr;

    //
    // Keep appending nodes until we get a non-PAIR cdr.
    //
    lastnptr = PMT_NIL;
    while (1) {
        expr = deserialize(sb); // read the car

        if (type == PST_DICT)
            nptr = dcons(expr, PMT_NIL); // build new cell
        else
            nptr = cons(expr, PMT_NIL); // build new cell

        if (is_null(lastnptr))
            val = nptr;
        else
            set_cdr(lastnptr, nptr);
        lastnptr = nptr;

        if (!deserialize_untagged_u8(&tag, sb)) // get tag of cdr
            throw exception("pmt::deserialize: malformed input stream", PMT_F);

        if (tag == PST_PAIR)
            continue; // keep on looping...

        if (tag == PST_NULL) {
            expr = PMT_NIL;
            break;
        }

        //
        // default: push tag back and use pmt_deserialize to get the cdr
        //
        sb.sungetc();
        expr = deserialize(sb);
        break;
    }

    //
    // At this point, expr contains the value of the final cdr in the list.
    //
    set_cdr(lastnptr, expr);
    return val;
}

} /* namespace pmt */