Skip to main content

rd_ast/
raw.rs

1//! Lossless fallback types for Rd structures that don't fit the
2//! [`RdNode::Tagged`](crate::RdNode::Tagged) or
3//! [`RdNode::Group`](crate::RdNode::Group) shapes.
4//!
5//! Raw's normative boundary and consumer treatment are in the crate's
6//! included `CONTRACT.md`.
7
8/// A losslessly-preserved Rd node that doesn't fit
9/// [`RdNode::Tagged`](crate::RdNode::Tagged) or
10/// [`RdNode::Group`](crate::RdNode::Group): it carries attributes beyond
11/// those the producer recognizes (for the RDS lowering, beyond
12/// `Rd_tag`/`Rd_option` plus the deliberately-discarded `srcref` -- see that
13/// module's validated attribute policy), or has another genuinely
14/// non-canonical shape (an untagged non-list value, or an untagged node
15/// carrying an `Rd_option`).
16#[derive(Debug, Clone, PartialEq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct RawRdNode {
19    /// The node's `Rd_tag` string, if any. `None` for an untagged
20    /// non-canonical node; ordinary positional list groups are represented by
21    /// [`crate::RdNode::Group`]. `Some(text)` preserves a
22    /// recognized-shape-but-unexpected node's original tag text exactly
23    /// (this is a different mechanism from
24    /// [`RdTag::Unknown`](crate::RdTag::Unknown), which lives inside a
25    /// well-formed [`RdNode::Tagged`](crate::RdNode::Tagged) node -- `Raw`
26    /// is for nodes whose *shape*, not just tag spelling, was unexpected).
27    tag: Option<String>,
28    /// The bracket-option content (`Rd_option`), if present.
29    option: Option<Vec<crate::RdNode>>,
30    /// The node's children.
31    children: Vec<crate::RdNode>,
32    /// The original node value when it cannot be represented as Rd children.
33    ///
34    /// `None` means the value is represented by `children`; `Some` preserves
35    /// an opaque value that has no child representation. A correct producer
36    /// never encodes the same value in both non-empty `children` and `payload`.
37    payload: Option<Box<RawRdValue>>,
38    /// Any attributes beyond `Rd_tag`/`Rd_option`, preserved verbatim.
39    attributes: Vec<RdAttribute>,
40}
41
42impl RawRdNode {
43    pub fn tag(&self) -> Option<&str> {
44        self.tag.as_deref()
45    }
46    pub fn option(&self) -> Option<&[crate::RdNode]> {
47        self.option.as_deref()
48    }
49    pub fn children(&self) -> &[crate::RdNode] {
50        &self.children
51    }
52    pub fn payload(&self) -> Option<&RawRdValue> {
53        self.payload.as_deref()
54    }
55    pub fn attributes(&self) -> &[RdAttribute] {
56        &self.attributes
57    }
58    /// Consumes the node into `(tag, option, children, payload, attributes)`.
59    #[allow(clippy::type_complexity)]
60    pub fn into_parts(
61        self,
62    ) -> (
63        Option<String>,
64        Option<Vec<crate::RdNode>>,
65        Vec<crate::RdNode>,
66        Option<RawRdValue>,
67        Vec<RdAttribute>,
68    ) {
69        (
70            self.tag,
71            self.option,
72            self.children,
73            self.payload.map(|payload| *payload),
74            self.attributes,
75        )
76    }
77}
78
79/// A single R-level attribute attached to a raw Rd node, preserved for
80/// losslessness.
81#[derive(Debug, Clone, PartialEq)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83pub struct RdAttribute {
84    /// The attribute's name.
85    name: String,
86    /// The attribute's value, including attributes attached to that value.
87    value: RawRdObject,
88}
89
90impl RdAttribute {
91    pub fn name(&self) -> &str {
92        &self.name
93    }
94    pub fn value(&self) -> &RawRdObject {
95        &self.value
96    }
97    pub fn into_parts(self) -> (String, RawRdObject) {
98        (self.name, self.value)
99    }
100}
101
102/// A recursively preserved object used by raw attribute values and lists.
103///
104/// Keeping the object's attributes alongside its value is important for
105/// structures such as `srcref`: the integer vector itself carries a nested
106/// `srcfile` attribute. This producer-agnostic representation retains that
107/// shape without exposing an RDS decoder type from `rd-ast`.
108#[derive(Debug, Clone, PartialEq)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110pub struct RawRdObject {
111    /// The object's value.
112    value: RawRdValue,
113    /// Attributes attached to this value, recursively preserved.
114    attributes: Vec<RdAttribute>,
115}
116
117impl RawRdObject {
118    pub fn value(&self) -> &RawRdValue {
119        &self.value
120    }
121    pub fn attributes(&self) -> &[RdAttribute] {
122        &self.attributes
123    }
124    pub fn into_parts(self) -> (RawRdValue, Vec<RdAttribute>) {
125        (self.value, self.attributes)
126    }
127}
128
129/// Advanced, lossless construction surface for AST producers.
130/// Normal consumers should inspect Raw values rather than synthesize them.
131pub mod producer {
132    use super::{RawRdNode, RawRdObject, RawRdValue, RdAttribute};
133    use crate::RdNode;
134
135    pub fn raw_node(
136        tag: Option<String>,
137        option: Option<Vec<RdNode>>,
138        children: Vec<RdNode>,
139        payload: Option<RawRdValue>,
140        attributes: Vec<RdAttribute>,
141    ) -> RawRdNode {
142        RawRdNode {
143            tag,
144            option,
145            children,
146            payload: payload.map(Box::new),
147            attributes,
148        }
149    }
150    pub fn raw_attribute(name: String, value: RawRdObject) -> RdAttribute {
151        RdAttribute { name, value }
152    }
153    pub fn raw_object(value: RawRdValue, attributes: Vec<RdAttribute>) -> RawRdObject {
154        RawRdObject { value, attributes }
155    }
156}
157
158/// The value of a recursively preserved raw object.
159#[derive(Debug, Clone, PartialEq)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
161#[non_exhaustive]
162pub enum RawRdValue {
163    /// R's `NULL` value.
164    Null,
165    /// An integer vector; `None` entries represent `NA`.
166    Integer(Vec<Option<i32>>),
167    /// A logical vector; `None` entries represent `NA`.
168    Logical(Vec<Option<bool>>),
169    /// A real vector with explicit representations for R's special values.
170    Real(Vec<RawRdReal>),
171    /// A character vector; `None` entries represent `NA`.
172    Character(Vec<Option<String>>),
173    /// A list whose elements retain their own attributes.
174    List(Vec<RawRdObject>),
175    /// A symbol name.
176    Symbol(String),
177    /// The string payload of a persisted reference.
178    Persisted(Vec<Option<String>>),
179    /// An opaque environment handle.
180    Environment(RawRdEnvironment),
181}
182
183/// The environment categories exposed by a producer when preserving a raw
184/// value. Environment frames are intentionally not part of the Rd AST.
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187#[non_exhaustive]
188pub enum RawRdEnvironment {
189    Global,
190    Base,
191    Empty,
192    Other,
193}
194
195/// A single value in an R real vector.
196///
197/// The special values use dedicated variants so serde formats such as JSON
198/// never collapse `NA`, `NaN`, and positive or negative infinity into the
199/// same `null` representation. This guarantee depends on
200/// [`Finite`](RawRdReal::Finite) itself only ever holding an actual finite
201/// payload -- see its documentation for the invariant.
202///
203/// # Why the invariant is documented rather than enforced
204///
205/// Nothing at the type level stops a caller from constructing
206/// `Finite(f64::NAN)` or `Finite(f64::INFINITY)`. Leaving that unenforced
207/// is a deliberate trade-off:
208///
209/// * Within this crate the only producer is the RDS lowering
210///   (`lower_raw_real` in `rds.rs`), which classifies every incoming value
211///   (`NA` / `NaN` / positive or negative infinity / finite) into the
212///   matching variant *before* construction, so lowering can never produce
213///   a non-finite `Finite`.
214/// * Empirically, real (double) vectors do not occur in real-world R help
215///   databases at all: Rd trees consist of character data plus
216///   string/integer/logical attributes (`Rd_tag`, `Rd_option`, `srcref`,
217///   `Rdfile`, `class`, `dynamicFlag`, `macro`). A full scan of an
218///   installed corpus (46 packages, 3457 topics, covering both values and
219///   attributes) found zero double vectors. This type exists for
220///   completeness of the lossless raw mirror, not because such data is
221///   expected in practice.
222/// * For JSON specifically, the deserialize direction is already safe:
223///   JSON numbers cannot express `NaN` or infinities, and
224///   `{"kind":"finite","value":null}` fails to deserialize as `f64`. The
225///   unenforced direction is serialization of a hand-constructed
226///   non-finite `Finite` (silently emitted as `null`), and round-tripping
227///   through binary serde formats that can represent non-finite floats.
228///
229/// Given the above, enforcement (a validated newtype payload plus a
230/// custom `Deserialize`) was deliberately deferred; if these raw types
231/// ever become a supported input surface for external producers, that is
232/// the intended hardening path.
233#[derive(Debug, Clone, Copy, PartialEq)]
234#[cfg_attr(
235    feature = "serde",
236    derive(serde::Serialize, serde::Deserialize),
237    serde(tag = "kind", content = "value", rename_all = "snake_case")
238)]
239pub enum RawRdReal {
240    /// R's `NA_real_` value.
241    Na,
242    /// A finite IEEE-754 value.
243    ///
244    /// This variant must only hold a finite payload; `NA`, `NaN`, and the
245    /// two infinities have their own dedicated variants ([`Na`](RawRdReal::Na),
246    /// [`Nan`](RawRdReal::Nan), [`PositiveInfinity`](RawRdReal::PositiveInfinity),
247    /// [`NegativeInfinity`](RawRdReal::NegativeInfinity)). Constructing
248    /// `Finite` with a non-finite value violates this contract: nothing
249    /// prevents it at the type level, but the derived serde `Serialize`
250    /// impl would then encode it as JSON `null` (`serde_json`'s standard
251    /// treatment of non-finite floats), silently defeating the point of
252    /// keeping these variants separate. See the type-level documentation
253    /// for why this invariant is documented rather than enforced.
254    Finite(f64),
255    /// A non-`NA` IEEE-754 not-a-number value.
256    Nan,
257    /// Positive infinity.
258    PositiveInfinity,
259    /// Negative infinity.
260    NegativeInfinity,
261}