Skip to main content

native/rx_rust_nif/src/codec.rs

use rustler::{Encoder, Env, Term};
use std::collections::HashSet;

const RX_DECODE_MAX_DEPTH: usize = 100;

#[allow(dead_code)]
#[derive(Clone)]
pub enum TaggedValue {
    Null,
    Logical(bool),
    Integer(i32),
    Double(f64),
    Character(Vec<u8>),
    LogicalVector(Vec<TaggedValue>),
    IntegerVector(Vec<TaggedValue>),
    DoubleVector(Vec<TaggedValue>),
    CharacterVector(Vec<TaggedValue>),
    Na(crate::work::NaType),
    NamedList(Vec<(Vec<u8>, TaggedValue)>),
    RList(Vec<(Option<Vec<u8>>, TaggedValue)>),
    Object(rustler::ResourceArc<crate::resource::RxResource>),
}

impl std::fmt::Debug for TaggedValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Null => f.write_str("Null"),
            Self::Logical(value) => f.debug_tuple("Logical").field(value).finish(),
            Self::Integer(value) => f.debug_tuple("Integer").field(value).finish(),
            Self::Double(value) => f.debug_tuple("Double").field(value).finish(),
            Self::Character(value) => f.debug_tuple("Character").field(value).finish(),
            Self::LogicalVector(values) => f.debug_tuple("LogicalVector").field(values).finish(),
            Self::IntegerVector(values) => f.debug_tuple("IntegerVector").field(values).finish(),
            Self::DoubleVector(values) => f.debug_tuple("DoubleVector").field(values).finish(),
            Self::CharacterVector(values) => {
                f.debug_tuple("CharacterVector").field(values).finish()
            }
            Self::Na(value) => f.debug_tuple("Na").field(value).finish(),
            Self::NamedList(entries) => f.debug_tuple("NamedList").field(entries).finish(),
            Self::RList(entries) => f.debug_tuple("RList").field(entries).finish(),
            Self::Object(_resource) => f.write_str("Object(..)"),
        }
    }
}

impl TaggedValue {
    pub fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
        match self {
            Self::Null => (crate::atoms::null(), crate::atoms::nil()).encode(env),
            Self::Logical(value) => (crate::atoms::logical(), *value).encode(env),
            Self::Integer(value) => (crate::atoms::integer(), *value).encode(env),
            Self::Double(value) => (crate::atoms::double(), *value).encode(env),
            Self::Character(bytes) => {
                let binary =
                    if let Some(mut out) = rustler::types::binary::OwnedBinary::new(bytes.len()) {
                        out.as_mut_slice().copy_from_slice(bytes);
                        out.release(env).encode(env)
                    } else {
                        "".encode(env)
                    };

                (crate::atoms::character(), binary).encode(env)
            }
            Self::LogicalVector(values) => {
                (crate::atoms::logical_vector(), encode_list(env, values)).encode(env)
            }
            Self::IntegerVector(values) => {
                (crate::atoms::integer_vector(), encode_list(env, values)).encode(env)
            }
            Self::DoubleVector(values) => {
                (crate::atoms::double_vector(), encode_list(env, values)).encode(env)
            }
            Self::CharacterVector(values) => {
                (crate::atoms::character_vector(), encode_list(env, values)).encode(env)
            }
            Self::Na(na_type) => (crate::atoms::na(), encode_na_type(env, *na_type)).encode(env),
            Self::NamedList(entries) => {
                let entries = entries
                    .iter()
                    .map(|(name, value)| (crate::terms::binary(env, name), value.encode(env)))
                    .collect::<Vec<_>>();
                (crate::atoms::named_list(), entries).encode(env)
            }
            Self::RList(entries) => {
                let entries = entries
                    .iter()
                    .map(|(name, value)| {
                        let name = name
                            .as_ref()
                            .map(|name| crate::terms::binary(env, name))
                            .unwrap_or_else(|| crate::atoms::nil().encode(env));
                        (name, value.encode(env))
                    })
                    .collect::<Vec<_>>();
                (crate::atoms::r_list(), entries).encode(env)
            }
            Self::Object(resource) => (crate::atoms::object(), resource).encode(env),
        }
    }
}

fn encode_list<'a>(env: Env<'a>, values: &[TaggedValue]) -> Vec<Term<'a>> {
    values.iter().map(|value| value.encode(env)).collect()
}

fn encode_na_type<'a>(env: Env<'a>, na_type: crate::work::NaType) -> Term<'a> {
    match na_type {
        crate::work::NaType::Logical => crate::atoms::logical().encode(env),
        crate::work::NaType::Integer => crate::atoms::integer().encode(env),
        crate::work::NaType::Double => crate::atoms::double().encode(env),
        crate::work::NaType::Character => crate::atoms::character().encode(env),
    }
}

pub fn parse_encode_value(
    kind: rustler::Term,
    value: rustler::Term,
) -> Result<crate::work::EncodeValue, rustler::Error> {
    let kind_name = if let Ok(atom) = kind.atom_to_string() {
        atom
    } else if let Ok(binary) = kind.decode::<String>() {
        binary
    } else {
        return Err(rustler::Error::BadArg);
    };

    match kind_name.as_str() {
        "null" => {
            if value.atom_to_string().ok().as_deref() == Some("nil") {
                Ok(crate::work::EncodeValue::Null)
            } else {
                Err(rustler::Error::BadArg)
            }
        }
        "logical" => value
            .decode::<bool>()
            .map(crate::work::EncodeValue::Logical),
        "integer" => value.decode::<i32>().map(crate::work::EncodeValue::Integer),
        "double" => {
            if value.is_float() {
                value.decode::<f64>().map(crate::work::EncodeValue::Double)
            } else {
                Err(rustler::Error::BadArg)
            }
        }
        "character" => value
            .decode::<rustler::Binary>()
            .map(|binary| crate::work::EncodeValue::Character(binary.as_slice().to_vec())),
        "logical_vector" => parse_logical_vector(value),
        "integer_vector" => parse_integer_vector(value),
        "double_vector" => parse_double_vector(value),
        "character_vector" => parse_character_vector(value),
        "na" => parse_na_type(value).map(crate::work::EncodeValue::Na),
        "named_list" => parse_named_list(value),
        _ => Err(rustler::Error::BadArg),
    }
}

fn parse_logical_vector(value: rustler::Term) -> Result<crate::work::EncodeValue, rustler::Error> {
    let values = value.decode::<Vec<rustler::Term>>()?;
    values
        .into_iter()
        .map(|term| term.decode::<bool>())
        .collect::<Result<Vec<_>, _>>()
        .map(crate::work::EncodeValue::LogicalVector)
}

fn parse_integer_vector(value: rustler::Term) -> Result<crate::work::EncodeValue, rustler::Error> {
    let values = value.decode::<Vec<rustler::Term>>()?;

    values
        .into_iter()
        .map(|term| {
            if term.is_integer() {
                term.decode::<i32>()
            } else {
                Err(rustler::Error::BadArg)
            }
        })
        .collect::<Result<Vec<_>, _>>()
        .map(crate::work::EncodeValue::IntegerVector)
}

fn parse_double_vector(value: rustler::Term) -> Result<crate::work::EncodeValue, rustler::Error> {
    let values = value.decode::<Vec<rustler::Term>>()?;

    values
        .into_iter()
        .map(|term| {
            if term.is_float() {
                term.decode::<f64>()
            } else {
                Err(rustler::Error::BadArg)
            }
        })
        .collect::<Result<Vec<_>, _>>()
        .map(crate::work::EncodeValue::DoubleVector)
}

fn parse_character_vector(
    value: rustler::Term,
) -> Result<crate::work::EncodeValue, rustler::Error> {
    let values = value.decode::<Vec<rustler::Term>>()?;

    values
        .into_iter()
        .map(|term| {
            term.decode::<rustler::Binary>()
                .map(|binary| binary.as_slice().to_vec())
        })
        .collect::<Result<Vec<_>, _>>()
        .map(crate::work::EncodeValue::CharacterVector)
}

fn parse_na_type(value: rustler::Term) -> Result<crate::work::NaType, rustler::Error> {
    let type_name = if let Ok(atom) = value.atom_to_string() {
        atom
    } else if let Ok(binary) = value.decode::<String>() {
        binary
    } else {
        return Err(rustler::Error::BadArg);
    };

    match type_name.as_str() {
        "logical" => Ok(crate::work::NaType::Logical),
        "integer" => Ok(crate::work::NaType::Integer),
        "double" => Ok(crate::work::NaType::Double),
        "character" => Ok(crate::work::NaType::Character),
        _ => Err(rustler::Error::BadArg),
    }
}

fn parse_named_list(value: rustler::Term) -> Result<crate::work::EncodeValue, rustler::Error> {
    let values = value.decode::<Vec<rustler::Term>>()?;
    let mut entries = Vec::with_capacity(values.len());

    for entry in values {
        let (name, resource) = entry.decode::<(
            rustler::Binary,
            rustler::ResourceArc<crate::resource::RxResource>,
        )>()?;
        entries.push((name.as_slice().to_vec(), resource));
    }

    Ok(crate::work::EncodeValue::NamedList(entries))
}

pub unsafe fn decode_simple_value(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
) -> Result<TaggedValue, String> {
    let kind = (api.type_of)(value);
    let len = (api.xlength)(value);

    match (kind, len) {
        (crate::r_api::NILSXP, _) => Ok(TaggedValue::Null),
        (crate::r_api::LGLSXP, 1) => Ok(TaggedValue::Logical((api.logical_elt)(value, 0) == 1)),
        (crate::r_api::INTSXP, 1) => Ok(TaggedValue::Integer((api.integer_elt)(value, 0))),
        (crate::r_api::REALSXP, 1) => Ok(TaggedValue::Double((api.real_elt)(value, 0))),
        (crate::r_api::STRSXP, 1) => {
            copy_string(api, (api.string_elt)(value, 0)).map(TaggedValue::Character)
        }
        _ => Err(format!(
            "R result type {kind} with length {len} is not supported by the native harness"
        )),
    }
}

pub fn do_encode_resource(
    state: &mut crate::owner::OwnerState,
    work: &crate::work::EncodeResourceWork,
) -> crate::work::WorkResult {
    if let Some(failure) = state.terminal_init_failure() {
        return crate::work::WorkResult::NativeInitFailed(failure);
    }

    if !state.is_initialized() {
        return crate::work::WorkResult::Error("embedded R runtime is not initialized".to_owned());
    }

    let Some(api) = state.r.as_ref() else {
        return crate::work::WorkResult::Error("embedded R API is not initialized".to_owned());
    };

    unsafe {
        match allocate_resource(api, &work.value) {
            Ok(resource) => crate::work::WorkResult::Resource(resource),
            Err(message) => crate::work::WorkResult::Error(message),
        }
    }
}

pub unsafe fn preserved_resource(
    api: &crate::r_api::RApi,
    sexp: crate::r_api::Sexp,
    kind: crate::resource::ResourceKind,
) -> rustler::ResourceArc<crate::resource::RxResource> {
    (api.preserve_object)(sexp);
    let resource = rustler::ResourceArc::new(crate::resource::RxResource::new(kind));
    resource.state.lock().expect("resource mutex poisoned").sexp = Some(sexp);
    resource
}

pub fn live_resource_sexp(
    resource: &rustler::ResourceArc<crate::resource::RxResource>,
) -> Result<crate::r_api::Sexp, String> {
    let state = resource
        .state
        .lock()
        .map_err(|_error| "native R object resource mutex was poisoned".to_owned())?;

    if resource
        .release_enqueued
        .load(std::sync::atomic::Ordering::SeqCst)
    {
        return Err("native R object resource has already been released".to_owned());
    }

    state
        .sexp
        .ok_or_else(|| "native R object resource has already been released".to_owned())
}

pub fn do_decode_resource(
    state: &mut crate::owner::OwnerState,
    work: &crate::work::DecodeResourceWork,
) -> crate::work::WorkResult {
    if let Some(failure) = state.terminal_init_failure() {
        return crate::work::WorkResult::NativeInitFailed(failure);
    }

    if !state.is_initialized() {
        return crate::work::WorkResult::Error("embedded R runtime is not initialized".to_owned());
    }

    let Some(api) = state.r.as_ref() else {
        return crate::work::WorkResult::Error("embedded R API is not initialized".to_owned());
    };

    let (sexp, resource_kind) = {
        let state = match work.resource.state.lock() {
            Ok(state) => state,
            Err(_error) => {
                return crate::work::WorkResult::Error(
                    "native R object resource mutex was poisoned".to_owned(),
                )
            }
        };

        if work
            .resource
            .release_enqueued
            .load(std::sync::atomic::Ordering::SeqCst)
        {
            return crate::work::WorkResult::Error(
                "native R object resource has already been released".to_owned(),
            );
        }

        match state.sexp {
            Some(sexp) => (sexp, state.kind),
            None => {
                return crate::work::WorkResult::Error(
                    "native R object resource has already been released".to_owned(),
                )
            }
        }
    };

    unsafe {
        match decode_resource_value(api, sexp, resource_kind) {
            Ok(value) => crate::work::WorkResult::Decoded(value),
            Err(message) => crate::work::WorkResult::TaggedError("unsupported_r_value", message),
        }
    }
}

unsafe fn allocate_resource(
    api: &crate::r_api::RApi,
    value: &crate::work::EncodeValue,
) -> Result<rustler::ResourceArc<crate::resource::RxResource>, String> {
    let mut protect_count = 0;

    let result = match value {
        crate::work::EncodeValue::Null => Ok((*api.nil_value, crate::resource::ResourceKind::Null)),
        crate::work::EncodeValue::Logical(value) => {
            let sexp = (api.protect)((api.scalar_logical)(if *value { 1 } else { 0 }));
            protect_count += 1;
            Ok((sexp, crate::resource::ResourceKind::Logical))
        }
        crate::work::EncodeValue::Integer(value) => {
            let sexp = (api.protect)((api.scalar_integer)(*value));
            protect_count += 1;
            Ok((sexp, crate::resource::ResourceKind::Integer))
        }
        crate::work::EncodeValue::Double(value) => {
            let sexp = (api.protect)((api.scalar_real)(*value));
            protect_count += 1;
            Ok((sexp, crate::resource::ResourceKind::Double))
        }
        crate::work::EncodeValue::Character(bytes) => {
            let string = bytes_to_c_string(bytes)?;
            let chars = (api.protect)((api.mk_char_len_ce)(
                string.as_ptr(),
                bytes.len() as i32,
                crate::r_api::CE_UTF8,
            ));
            protect_count += 1;
            let sexp = (api.protect)((api.scalar_string)(chars));
            protect_count += 1;
            Ok((sexp, crate::resource::ResourceKind::Character))
        }
        crate::work::EncodeValue::LogicalVector(values) => {
            let sexp = (api.protect)((api.alloc_vector)(
                crate::r_api::LGLSXP,
                values.len() as isize,
            ));
            protect_count += 1;

            for (index, value) in values.iter().enumerate() {
                (api.set_logical_elt)(sexp, index as isize, if *value { 1 } else { 0 });
            }

            Ok((sexp, crate::resource::ResourceKind::LogicalVector))
        }
        crate::work::EncodeValue::IntegerVector(values) => {
            let sexp = (api.protect)((api.alloc_vector)(
                crate::r_api::INTSXP,
                values.len() as isize,
            ));
            protect_count += 1;

            for (index, value) in values.iter().enumerate() {
                (api.set_integer_elt)(sexp, index as isize, *value);
            }

            Ok((sexp, crate::resource::ResourceKind::IntegerVector))
        }
        crate::work::EncodeValue::DoubleVector(values) => {
            let sexp = (api.protect)((api.alloc_vector)(
                crate::r_api::REALSXP,
                values.len() as isize,
            ));
            protect_count += 1;

            for (index, value) in values.iter().enumerate() {
                (api.set_real_elt)(sexp, index as isize, *value);
            }

            Ok((sexp, crate::resource::ResourceKind::DoubleVector))
        }
        crate::work::EncodeValue::CharacterVector(values) => {
            let sexp = (api.protect)((api.alloc_vector)(
                crate::r_api::STRSXP,
                values.len() as isize,
            ));
            protect_count += 1;

            for (index, value) in values.iter().enumerate() {
                let string = bytes_to_c_string(value)?;
                let chars = (api.protect)((api.mk_char_len_ce)(
                    string.as_ptr(),
                    value.len() as i32,
                    crate::r_api::CE_UTF8,
                ));
                (api.set_string_elt)(sexp, index as isize, chars);
                (api.unprotect)(1);
            }

            Ok((sexp, crate::resource::ResourceKind::CharacterVector))
        }
        crate::work::EncodeValue::Na(na_type) => allocate_na(api, *na_type).map(|sexp| {
            let kind = match na_type {
                crate::work::NaType::Logical => crate::resource::ResourceKind::Logical,
                crate::work::NaType::Integer => crate::resource::ResourceKind::Integer,
                crate::work::NaType::Double => crate::resource::ResourceKind::Double,
                crate::work::NaType::Character => crate::resource::ResourceKind::Character,
            };
            protect_count += 1;
            (sexp, kind)
        }),
        crate::work::EncodeValue::NamedList(entries) => {
            let sexp = (api.protect)((api.alloc_vector)(
                crate::r_api::VECSXP,
                entries.len() as isize,
            ));
            protect_count += 1;

            let names = (api.protect)((api.alloc_vector)(
                crate::r_api::STRSXP,
                entries.len() as isize,
            ));
            protect_count += 1;

            for (index, (name, resource)) in entries.iter().enumerate() {
                let name_string = bytes_to_c_string(name)?;
                let chars = (api.protect)((api.mk_char_len_ce)(
                    name_string.as_ptr(),
                    name.len() as i32,
                    crate::r_api::CE_UTF8,
                ));
                (api.set_string_elt)(names, index as isize, chars);
                (api.unprotect)(1);

                let value = live_resource_sexp(resource)?;
                (api.set_vector_elt)(sexp, index as isize, value);
            }

            (api.set_attrib)(sexp, *api.names_symbol, names);
            Ok((sexp, crate::resource::ResourceKind::Generic))
        }
    };

    match result {
        Ok((sexp, kind)) => {
            let resource = preserved_resource(api, sexp, kind);
            (api.unprotect)(protect_count);
            Ok(resource)
        }
        Err(message) => {
            (api.unprotect)(protect_count);
            Err(message)
        }
    }
}

unsafe fn allocate_na(
    api: &crate::r_api::RApi,
    na_type: crate::work::NaType,
) -> Result<crate::r_api::Sexp, String> {
    let sexp = match na_type {
        crate::work::NaType::Logical => (api.protect)((api.scalar_logical)(*api.na_int)),
        crate::work::NaType::Integer => (api.protect)((api.scalar_integer)(*api.na_int)),
        crate::work::NaType::Double => (api.protect)((api.scalar_real)(*api.na_real)),
        crate::work::NaType::Character => (api.protect)((api.scalar_string)(*api.na_string)),
    };

    Ok(sexp)
}

fn bytes_to_c_string(bytes: &[u8]) -> Result<std::ffi::CString, String> {
    std::ffi::CString::new(bytes)
        .map_err(|error| format!("native character value contains NUL byte: {error}"))
}

unsafe fn decode_resource_value(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    resource_kind: crate::resource::ResourceKind,
) -> Result<TaggedValue, String> {
    let kind = (api.type_of)(value);
    let len = (api.xlength)(value);

    if value == *api.nil_value || kind == crate::r_api::NILSXP {
        return Ok(TaggedValue::Null);
    }

    match resource_kind {
        crate::resource::ResourceKind::LogicalVector => {
            return decode_logical_vector(api, value, len)
        }
        crate::resource::ResourceKind::IntegerVector => {
            return decode_integer_vector(api, value, len)
        }
        crate::resource::ResourceKind::DoubleVector => {
            return decode_double_vector(api, value, len)
        }
        crate::resource::ResourceKind::CharacterVector => {
            return decode_character_vector(api, value, len)
        }
        _other => {}
    }

    decode_r_value(api, value, 0)
}

unsafe fn decode_r_value(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    depth: usize,
) -> Result<TaggedValue, String> {
    let kind = (api.type_of)(value);
    let len = (api.xlength)(value);

    if value == *api.nil_value || kind == crate::r_api::NILSXP {
        return Ok(TaggedValue::Null);
    }

    if depth >= RX_DECODE_MAX_DEPTH || is_semantic_object(api, value) {
        return decode_opaque_value(api, value, depth, kind);
    }

    match kind {
        crate::r_api::LGLSXP => decode_logical(api, value, len),
        crate::r_api::INTSXP => decode_integer(api, value, len),
        crate::r_api::REALSXP => decode_double(api, value, len),
        crate::r_api::STRSXP => decode_character(api, value, len),
        crate::r_api::VECSXP => decode_r_list(api, value, depth),
        _ => decode_opaque_value(api, value, depth, kind),
    }
}

unsafe fn decode_opaque_value(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    depth: usize,
    kind: i32,
) -> Result<TaggedValue, String> {
    if depth == 0 {
        return Err(format!(
            "R result type {kind} is not supported by the native harness"
        ));
    }

    Ok(TaggedValue::Object(preserved_resource(
        api,
        value,
        crate::resource::ResourceKind::Generic,
    )))
}

unsafe fn decode_r_list(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    depth: usize,
) -> Result<TaggedValue, String> {
    let len = (api.xlength)(value);
    let capacity = usize_len(len)?;
    let names = (api.get_attrib)(value, *api.names_symbol);
    let has_valid_names = names != *api.nil_value
        && (api.type_of)(names) == crate::r_api::STRSXP
        && (api.xlength)(names) == len;
    let mut fully_named = capacity == 0 || has_valid_names;
    let mut seen_names = HashSet::with_capacity(capacity);
    let mut entries = Vec::with_capacity(capacity);

    for index in 0..len {
        let name = if has_valid_names {
            copy_list_name(api, names, index)?
        } else {
            None
        };

        if let Some(name) = name.as_ref() {
            if !seen_names.insert(name.clone()) {
                fully_named = false;
            }
        } else {
            fully_named = false;
        }

        let decoded = decode_r_value(api, (api.vector_elt)(value, index), depth + 1)?;
        entries.push((name, decoded));
    }

    if fully_named {
        Ok(TaggedValue::NamedList(
            entries
                .into_iter()
                .map(|(name, value)| {
                    (
                        name.expect("fully named list entry missing name after validation"),
                        value,
                    )
                })
                .collect(),
        ))
    } else {
        Ok(TaggedValue::RList(entries))
    }
}

unsafe fn copy_list_name(
    api: &crate::r_api::RApi,
    names: crate::r_api::Sexp,
    index: isize,
) -> Result<Option<Vec<u8>>, String> {
    let name = (api.string_elt)(names, index);

    if name == *api.na_string {
        return Ok(None);
    }

    let bytes = copy_string(api, name)?;
    if bytes.is_empty() {
        Ok(None)
    } else {
        Ok(Some(bytes))
    }
}

unsafe fn is_semantic_object(api: &crate::r_api::RApi, value: crate::r_api::Sexp) -> bool {
    has_non_empty_attribute(api, value, *api.class_symbol)
        || has_non_empty_attribute(api, value, *api.dim_symbol)
}

unsafe fn has_non_empty_attribute(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    symbol: crate::r_api::Sexp,
) -> bool {
    if symbol.is_null() || value == *api.nil_value {
        return false;
    }

    let attr = (api.get_attrib)(value, symbol);
    attr != *api.nil_value && (api.xlength)(attr) > 0
}

unsafe fn decode_logical(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    len: isize,
) -> Result<TaggedValue, String> {
    if len == 1 {
        return Ok(tagged_logical(api, (api.logical_elt)(value, 0)));
    }

    decode_logical_vector(api, value, len)
}

unsafe fn decode_logical_vector(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    len: isize,
) -> Result<TaggedValue, String> {
    let mut values = Vec::with_capacity(usize_len(len)?);
    for index in 0..len {
        values.push(tagged_logical(api, (api.logical_elt)(value, index)));
    }
    Ok(TaggedValue::LogicalVector(values))
}

unsafe fn decode_integer(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    len: isize,
) -> Result<TaggedValue, String> {
    if len == 1 {
        return Ok(tagged_integer(api, (api.integer_elt)(value, 0)));
    }

    decode_integer_vector(api, value, len)
}

unsafe fn decode_integer_vector(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    len: isize,
) -> Result<TaggedValue, String> {
    let mut values = Vec::with_capacity(usize_len(len)?);
    for index in 0..len {
        values.push(tagged_integer(api, (api.integer_elt)(value, index)));
    }
    Ok(TaggedValue::IntegerVector(values))
}

unsafe fn decode_double(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    len: isize,
) -> Result<TaggedValue, String> {
    if len == 1 {
        return Ok(tagged_double((api.real_elt)(value, 0)));
    }

    decode_double_vector(api, value, len)
}

unsafe fn decode_double_vector(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    len: isize,
) -> Result<TaggedValue, String> {
    let mut values = Vec::with_capacity(usize_len(len)?);
    for index in 0..len {
        values.push(tagged_double((api.real_elt)(value, index)));
    }
    Ok(TaggedValue::DoubleVector(values))
}

unsafe fn decode_character(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    len: isize,
) -> Result<TaggedValue, String> {
    if len == 1 {
        return tagged_character(api, (api.string_elt)(value, 0));
    }

    decode_character_vector(api, value, len)
}

unsafe fn decode_character_vector(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
    len: isize,
) -> Result<TaggedValue, String> {
    let mut values = Vec::with_capacity(usize_len(len)?);
    for index in 0..len {
        values.push(tagged_character(api, (api.string_elt)(value, index))?);
    }
    Ok(TaggedValue::CharacterVector(values))
}

unsafe fn tagged_logical(api: &crate::r_api::RApi, value: i32) -> TaggedValue {
    if value == *api.na_int {
        TaggedValue::Na(crate::work::NaType::Logical)
    } else {
        TaggedValue::Logical(value == 1)
    }
}

unsafe fn tagged_integer(api: &crate::r_api::RApi, value: i32) -> TaggedValue {
    if value == *api.na_int {
        TaggedValue::Na(crate::work::NaType::Integer)
    } else {
        TaggedValue::Integer(value)
    }
}

fn tagged_double(value: f64) -> TaggedValue {
    if value.is_nan() {
        TaggedValue::Na(crate::work::NaType::Double)
    } else {
        TaggedValue::Double(value)
    }
}

unsafe fn tagged_character(
    api: &crate::r_api::RApi,
    value: crate::r_api::Sexp,
) -> Result<TaggedValue, String> {
    if value == *api.na_string {
        Ok(TaggedValue::Na(crate::work::NaType::Character))
    } else {
        copy_string(api, value).map(TaggedValue::Character)
    }
}

fn usize_len(len: isize) -> Result<usize, String> {
    usize::try_from(len).map_err(|_| "native vector result is too large".to_owned())
}

pub unsafe fn copy_string(
    api: &crate::r_api::RApi,
    string: crate::r_api::Sexp,
) -> Result<Vec<u8>, String> {
    let ptr = (api.r_char)(string);

    if ptr.is_null() {
        return Ok(Vec::new());
    }

    let len = (api.length)(string);
    if len <= 0 {
        return Ok(Vec::new());
    }

    let bytes = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
    Ok(bytes.to_vec())
}