1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Wrapper for R output streams.

use crate::{catch_r_error, error::Error, error::Result, robj::GetSexp};
use libR_sys::*;
use std::io::Write;

use super::PstreamFormat;

/// The hook will convert some objects into strings.
pub struct WriteHook {
    pub func: unsafe extern "C" fn(arg1: SEXP, arg2: SEXP) -> SEXP,
    pub data: SEXP,
}

pub struct OutStream<W: Write> {
    r_state: R_outpstream_st,
    writer: W,
}

impl<W: Write> OutStream<W> {
    pub fn from_writer(
        writer: W,
        format: PstreamFormat,
        version: i32,
        hook: Option<WriteHook>,
    ) -> Box<OutStream<W>> {
        unsafe extern "C" fn outchar<W: Write>(arg1: R_outpstream_t, arg2: ::std::os::raw::c_int) {
            let writer = &mut *((*arg1).data as *mut W);
            let b = [arg2 as u8];
            writer.write_all(&b).unwrap();
        }

        unsafe extern "C" fn outbytes<W: Write>(
            arg1: R_outpstream_t,
            arg2: *mut ::std::os::raw::c_void,
            arg3: ::std::os::raw::c_int,
        ) {
            let writer = &mut *((*arg1).data as *mut W);
            let b = std::slice::from_raw_parts(arg2 as *mut u8, arg3 as usize);
            writer.write_all(b).unwrap();
        }

        {
            let (hook_fn, hook_data) = if let Some(WriteHook { func, data }) = hook {
                (Some(func), data)
            } else {
                unsafe { (None, R_NilValue) }
            };

            let r_state = libR_sys::R_outpstream_st {
                data: std::ptr::null_mut(),
                type_: format as R_pstream_format_t,
                version,
                OutChar: Some(outchar::<W>),
                OutBytes: Some(outbytes::<W>),
                OutPersistHookFunc: hook_fn,
                OutPersistHookData: hook_data,
            };
            let mut os = Box::new(OutStream { r_state, writer });
            os.r_state.data = &mut os.writer as *mut W as R_pstream_data_t;
            os
        }
    }
}

pub trait Save: GetSexp {
    /// Save an object in the R data format.
    /// `version` should probably be 3.
    fn save<P: AsRef<std::path::Path>>(
        &self,
        path: &P,
        format: PstreamFormat,
        version: i32,
        hook: Option<WriteHook>,
    ) -> Result<()> {
        let mut writer = std::fs::File::create(path.as_ref())
            .map_err(|_| Error::Other(format!("could not create file {:?}", path.as_ref())))?;
        self.to_writer(&mut writer, format, version, hook)
    }

    /// Save an object in the R data format to a `Write` trait.
    /// `version` should probably be 3.
    fn to_writer<W: Write>(
        &self,
        writer: &mut W,
        format: PstreamFormat,
        version: i32,
        hook: Option<WriteHook>,
    ) -> Result<()> {
        let mut os = OutStream::from_writer(writer, format, version, hook);

        let stream = &mut os.r_state as R_outpstream_t;
        let sexp = unsafe { self.get() };
        if !(2..=3).contains(&version) {
            return Err(Error::Other(format!(
                "version must be 2 or 3, got {:?}",
                version
            )));
        }

        catch_r_error(move || unsafe {
            R_Serialize(sexp, stream);
            R_NilValue
        })?;
        Ok(())
    }
}

impl<R: GetSexp> Save for R {}