1use super::PstreamFormat;
4use crate::{catch_r_error, error::Error, error::Result, robj::GetSexp, throw_r_error};
5use extendr_ffi::{
6 R_NilValue, R_Serialize, R_outpstream_st, R_outpstream_t, R_pstream_data_t, R_pstream_format_t,
7 SEXP,
8};
9use std::io::Write;
10
11pub struct WriteHook {
13 pub func: unsafe extern "C" fn(arg1: SEXP, arg2: SEXP) -> SEXP,
14 pub data: SEXP,
15}
16
17pub struct OutStream<W: Write> {
18 r_state: R_outpstream_st,
19 writer: W,
20}
21
22impl<W: Write> OutStream<W> {
23 pub fn from_writer(
24 writer: W,
25 format: PstreamFormat,
26 version: i32,
27 hook: Option<WriteHook>,
28 ) -> Box<OutStream<W>> {
29 unsafe extern "C" fn outchar<W: Write>(arg1: R_outpstream_t, arg2: ::std::os::raw::c_int) {
30 let writer = &mut *((*arg1).data as *mut W);
31 let b = [arg2 as u8];
32 if let Err(e) = writer.write_all(&b) {
33 throw_r_error(format!("failed to write R serialization stream: {e}"));
34 }
35 }
36
37 unsafe extern "C" fn outbytes<W: Write>(
38 arg1: R_outpstream_t,
39 arg2: *mut ::std::os::raw::c_void,
40 arg3: ::std::os::raw::c_int,
41 ) {
42 let writer = &mut *((*arg1).data as *mut W);
43 let b = std::slice::from_raw_parts(arg2 as *mut u8, arg3 as usize);
44 if let Err(e) = writer.write_all(b) {
45 throw_r_error(format!("failed to write R serialization stream: {e}"));
46 }
47 }
48
49 {
50 let (hook_fn, hook_data) = if let Some(WriteHook { func, data }) = hook {
51 (Some(func), data)
52 } else {
53 unsafe { (None, R_NilValue) }
54 };
55
56 let r_state = extendr_ffi::R_outpstream_st {
57 data: std::ptr::null_mut(),
58 type_: format as R_pstream_format_t,
59 version,
60 OutChar: Some(outchar::<W>),
61 OutBytes: Some(outbytes::<W>),
62 OutPersistHookFunc: hook_fn,
63 OutPersistHookData: hook_data,
64 };
65 let mut os = Box::new(OutStream { r_state, writer });
66 os.r_state.data = &mut os.writer as *mut W as R_pstream_data_t;
67 os
68 }
69 }
70}
71
72pub trait Save: GetSexp {
73 fn save<P: AsRef<std::path::Path>>(
76 &self,
77 path: &P,
78 format: PstreamFormat,
79 version: i32,
80 hook: Option<WriteHook>,
81 ) -> Result<()> {
82 let mut writer = std::fs::File::create(path.as_ref())
83 .map_err(|_| Error::Other(format!("could not create file {:?}", path.as_ref())))?;
84 self.to_writer(&mut writer, format, version, hook)
85 }
86
87 fn to_writer<W: Write>(
90 &self,
91 writer: &mut W,
92 format: PstreamFormat,
93 version: i32,
94 hook: Option<WriteHook>,
95 ) -> Result<()> {
96 let mut os = OutStream::from_writer(writer, format, version, hook);
97
98 let stream = &mut os.r_state as R_outpstream_t;
99 let sexp = unsafe { self.get() };
100 if !(2..=3).contains(&version) {
101 return Err(Error::Other(format!(
102 "version must be 2 or 3, got {:?}",
103 version
104 )));
105 }
106
107 catch_r_error(move || unsafe {
108 R_Serialize(sexp, stream);
109 R_NilValue
110 })?;
111 Ok(())
112 }
113}
114
115impl<R: GetSexp> Save for R {}