Skip to main content

rd_rds/
value.rs

1use std::{borrow::Cow, sync::Arc};
2
3use crate::Error;
4
5#[derive(Debug, Clone, PartialEq)]
6#[non_exhaustive]
7pub struct RObject {
8    value: RValue,
9    attributes: Attributes,
10}
11
12#[derive(Debug, Clone, PartialEq)]
13#[non_exhaustive]
14pub enum RValue {
15    Null,
16    Logical(Vec<Option<bool>>),
17    Integer(Vec<Option<i32>>),
18    Real(Vec<Option<f64>>),
19    Character(Vec<RStr>),
20    List(Vec<RObject>),
21    Symbol(Symbol),
22    Persisted(Persisted),
23    Environment(EnvHandle),
24}
25
26/// An opaque handle to an environment.
27///
28/// `rd-rds` never models environment frames or enclosures: it only consumes
29/// the wire bytes correctly and keeps the reference table aligned. All
30/// non-singleton environments collapse to [`EnvHandle::Other`].
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum EnvHandle {
34    /// `R_GlobalEnv`.
35    Global,
36    /// `R_BaseEnv` / `R_BaseNamespace`.
37    Base,
38    /// `R_EmptyEnv`.
39    Empty,
40    /// Any other environment/package/namespace. Its wire fields were
41    /// decoded (to keep the stream and reference table in sync) and then
42    /// discarded.
43    Other,
44}
45
46#[derive(Debug, Clone, PartialEq)]
47pub struct Attribute {
48    name: Symbol,
49    value: RObject,
50}
51
52impl Attribute {
53    pub fn new(name: Symbol, value: RObject) -> Self {
54        Self { name, value }
55    }
56
57    pub fn name(&self) -> &Symbol {
58        &self.name
59    }
60
61    pub fn value(&self) -> &RObject {
62        &self.value
63    }
64}
65
66#[derive(Debug, Clone, PartialEq, Default)]
67pub struct Attributes(Vec<Attribute>);
68
69impl Attributes {
70    /// Constructs an attribute collection from decoded attributes.
71    pub fn new(values: Vec<Attribute>) -> Self {
72        Self(values)
73    }
74
75    pub fn is_empty(&self) -> bool {
76        self.0.is_empty()
77    }
78
79    pub fn len(&self) -> usize {
80        self.0.len()
81    }
82
83    pub fn iter(&self) -> impl Iterator<Item = &Attribute> {
84        self.0.iter()
85    }
86
87    pub fn get(&self, name: &str) -> Option<&RObject> {
88        self.0
89            .iter()
90            .find(|attribute| attribute.name().as_str() == name)
91            .map(|attribute| attribute.value())
92    }
93}
94
95impl RObject {
96    pub fn from_parts(value: RValue, attributes: Attributes) -> Self {
97        Self { value, attributes }
98    }
99
100    pub fn value(&self) -> &RValue {
101        &self.value
102    }
103
104    pub fn attributes(&self) -> &Attributes {
105        &self.attributes
106    }
107
108    pub fn into_parts(self) -> (RValue, Attributes) {
109        (self.value, self.attributes)
110    }
111
112    pub fn class(&self) -> Option<&[RStr]> {
113        match self.attributes.get("class")?.value() {
114            RValue::Character(values) => Some(values),
115            _ => None,
116        }
117    }
118
119    pub fn names(&self) -> Option<&[RStr]> {
120        match self.attributes.get("names")?.value() {
121            RValue::Character(values) => Some(values),
122            _ => None,
123        }
124    }
125
126    pub fn get_named(&self, name: &str) -> Option<&RObject> {
127        let RValue::List(items) = self.value() else {
128            return None;
129        };
130        let names = self.names()?;
131        names
132            .iter()
133            .position(|value| value.as_str().and_then(Result::ok).as_deref() == Some(name))
134            .and_then(|index| items.get(index))
135    }
136}
137
138/// A symbol whose print name is decoded eagerly during parsing, so it always
139/// holds valid text.
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct Symbol(Arc<str>);
142
143impl Symbol {
144    pub fn new(value: impl Into<Arc<str>>) -> Self {
145        Self(value.into())
146    }
147
148    pub fn as_str(&self) -> &str {
149        &self.0
150    }
151}
152
153impl From<&str> for Symbol {
154    fn from(value: &str) -> Self {
155        Self::new(value)
156    }
157}
158
159#[derive(Debug, Clone, PartialEq)]
160pub struct Persisted(Arc<[RStr]>);
161
162impl Persisted {
163    pub(crate) fn new(values: Vec<RStr>) -> Self {
164        Self(values.into())
165    }
166
167    #[cfg(test)]
168    pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
169        Arc::ptr_eq(&self.0, &other.0)
170    }
171
172    pub fn as_slice(&self) -> &[RStr] {
173        &self.0
174    }
175}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
178#[non_exhaustive]
179pub enum RStr {
180    Na,
181    Value(RStrValue),
182}
183
184/// The source of native-encoding context used to interpret an R string.
185#[derive(Debug, Clone, PartialEq, Eq)]
186#[non_exhaustive]
187pub enum NativeEncodingSource {
188    /// The stream header declared this native encoding name (format 3 only).
189    Header(Arc<str>),
190    /// The header carried no native encoding and the caller opted into
191    /// [`crate::NativeEncodingPolicy::AssumeUtf8`].
192    AssumedUtf8,
193    /// The header carried no native encoding and the caller made no
194    /// assumption.
195    Unknown,
196}
197
198impl NativeEncodingSource {
199    pub(crate) fn decodes_as_utf8(&self) -> bool {
200        match self {
201            Self::Header(name) => is_utf8_native_encoding(Some(name)),
202            Self::AssumedUtf8 => true,
203            Self::Unknown => false,
204        }
205    }
206}
207
208/// The opaque payload of an [`RStr::Value`] string.
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct RStrValue {
211    bytes: Arc<[u8]>,
212    encoding: REncoding,
213    native_encoding_source: NativeEncodingSource,
214}
215
216impl RStr {
217    /// Constructs an R string value from its serialized bytes and encoding
218    /// context.
219    pub fn new(
220        bytes: &[u8],
221        encoding: REncoding,
222        native_encoding_source: NativeEncodingSource,
223    ) -> Self {
224        Self::Value(RStrValue {
225            bytes: Arc::from(bytes),
226            encoding,
227            native_encoding_source,
228        })
229    }
230
231    pub fn as_str(&self) -> Option<Result<Cow<'_, str>, Error>> {
232        match self {
233            Self::Na => None,
234            Self::Value(value) => Some(value.as_str()),
235        }
236    }
237
238    /// Reports the CHARSXP encoding flag recorded in the serialized data.
239    /// R's `Encoding()` normalizes ASCII strings to "unknown" after reading,
240    /// so the two can differ while the decoded string contents agree.
241    pub fn encoding(&self) -> Option<REncoding> {
242        match self {
243            Self::Na => None,
244            Self::Value(value) => Some(value.encoding()),
245        }
246    }
247}
248
249impl RStrValue {
250    fn as_str(&self) -> Result<Cow<'_, str>, Error> {
251        match self.encoding {
252            REncoding::Native => {
253                if self.bytes.is_ascii() || self.native_encoding_source.decodes_as_utf8() {
254                    std::str::from_utf8(&self.bytes)
255                        .map(Cow::Borrowed)
256                        .map_err(|_| Error::InvalidStringEncoding)
257                } else {
258                    Err(Error::InvalidStringEncoding)
259                }
260            }
261            REncoding::Utf8 => std::str::from_utf8(&self.bytes)
262                .map(Cow::Borrowed)
263                .map_err(|_| Error::InvalidStringEncoding),
264            REncoding::Latin1 => Ok(Cow::Owned(
265                self.bytes.iter().map(|&byte| byte as char).collect(),
266            )),
267            REncoding::Bytes => Err(Error::InvalidStringEncoding),
268        }
269    }
270
271    /// Returns the serialized string bytes without converting them.
272    pub fn bytes(&self) -> &[u8] {
273        &self.bytes
274    }
275
276    /// Returns the CHARSXP encoding flag recorded in the serialized data.
277    pub fn encoding(&self) -> REncoding {
278        self.encoding
279    }
280
281    /// Returns the native-encoding provenance used for this value.
282    pub fn native_encoding_source(&self) -> &NativeEncodingSource {
283        &self.native_encoding_source
284    }
285
286    /// Returns the native encoding name declared by the stream header, if any.
287    ///
288    /// This reports header evidence only; it is not the effective decoding
289    /// context and therefore returns `None` for [`NativeEncodingSource::AssumedUtf8`]
290    /// and [`NativeEncodingSource::Unknown`].
291    pub fn header_native_encoding(&self) -> Option<&str> {
292        match &self.native_encoding_source {
293            NativeEncodingSource::Header(name) => Some(name),
294            NativeEncodingSource::AssumedUtf8 | NativeEncodingSource::Unknown => None,
295        }
296    }
297}
298
299fn is_utf8_native_encoding(encoding: Option<&str>) -> bool {
300    encoding
301        .map(|encoding| {
302            encoding.eq_ignore_ascii_case("UTF-8") || encoding.eq_ignore_ascii_case("UTF8")
303        })
304        .unwrap_or(false)
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308#[non_exhaustive]
309pub enum REncoding {
310    Native,
311    Utf8,
312    Latin1,
313    Bytes,
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn native_ascii_strings_decode_without_header_encoding() {
322        let value = RStr::new(b"name", REncoding::Native, NativeEncodingSource::Unknown);
323        assert_eq!(value.as_str().unwrap().unwrap().as_ref(), "name");
324    }
325
326    #[test]
327    fn native_utf8_strings_decode_when_header_encoding_is_utf8() {
328        let value = RStr::new(
329            "cafe\u{301}".as_bytes(),
330            REncoding::Native,
331            NativeEncodingSource::Header(Arc::from("UTF-8")),
332        );
333        assert_eq!(value.as_str().unwrap().unwrap().as_ref(), "cafe\u{301}");
334    }
335
336    #[test]
337    fn native_non_ascii_strings_reject_unknown_native_encoding() {
338        let value = RStr::new(
339            "é".as_bytes(),
340            REncoding::Native,
341            NativeEncodingSource::Unknown,
342        );
343        assert_eq!(
344            value.as_str().unwrap().unwrap_err(),
345            Error::InvalidStringEncoding
346        );
347    }
348}
349
350#[derive(Debug, Clone, Copy)]
351pub struct Limits {
352    max_depth: u32,
353    max_vector_len: usize,
354    max_total_elements: usize,
355    max_references: usize,
356}
357
358impl Default for Limits {
359    fn default() -> Self {
360        Self {
361            max_depth: 5_000,
362            max_vector_len: 8_000_000,
363            max_total_elements: 16_000_000,
364            max_references: 16_000_000,
365        }
366    }
367}
368
369impl Limits {
370    pub fn max_depth(mut self, value: u32) -> Self {
371        self.max_depth = value;
372        self
373    }
374
375    pub fn max_vector_len(mut self, value: usize) -> Self {
376        self.max_vector_len = value;
377        self
378    }
379
380    pub fn max_total_elements(mut self, value: usize) -> Self {
381        self.max_total_elements = value;
382        self
383    }
384
385    /// Sets the maximum number of entries in the serialized reference table.
386    #[must_use]
387    pub fn max_references(mut self, value: usize) -> Self {
388        self.max_references = value;
389        self
390    }
391
392    pub(crate) fn max_depth_value(self) -> u32 {
393        self.max_depth
394    }
395    pub(crate) fn max_vector_len_value(self) -> usize {
396        self.max_vector_len
397    }
398    pub(crate) fn max_total_elements_value(self) -> usize {
399        self.max_total_elements
400    }
401    pub(crate) fn max_references_value(self) -> usize {
402        self.max_references
403    }
404}
405
406#[derive(Debug, Clone, Copy, PartialEq, Eq)]
407#[non_exhaustive]
408pub enum SexpKind {
409    Nil,
410    Sym,
411    PairList,
412    Env,
413    Char,
414    Logical,
415    Integer,
416    Real,
417    String,
418    List,
419    ExtPtr,
420    WeakRef,
421    Persist,
422    Package,
423    Namespace,
424    Ref,
425    Closure,
426    BuiltIn,
427    Special,
428    Promise,
429    Lang,
430    DotDotDot,
431    ByteCode,
432    S4,
433    Complex,
434    Raw,
435    Other(u8),
436}
437
438impl SexpKind {
439    pub fn from_type_code(type_code: u8) -> Self {
440        match type_code {
441            0 | 254 => Self::Nil,
442            1 => Self::Sym,
443            2 => Self::PairList,
444            3 => Self::Closure,
445            4 => Self::Env,
446            5 => Self::Promise,
447            6 => Self::Lang,
448            7 => Self::Special,
449            8 => Self::BuiltIn,
450            9 => Self::Char,
451            10 => Self::Logical,
452            13 => Self::Integer,
453            14 => Self::Real,
454            15 => Self::Complex,
455            16 => Self::String,
456            17 => Self::DotDotDot,
457            19 => Self::List,
458            21 => Self::ByteCode,
459            22 => Self::ExtPtr,
460            23 => Self::WeakRef,
461            24 => Self::Raw,
462            25 => Self::S4,
463            247 => Self::Persist,
464            248 => Self::Package,
465            249 | 250 => Self::Namespace,
466            255 => Self::Ref,
467            other => Self::Other(other),
468        }
469    }
470}