Skip to main content

rd_ast/view/
dynamic.rs

1use super::*;
2
3/// A borrowed, structurally valid `\\Sexpr{code}` view.
4#[derive(Debug, Clone, PartialEq)]
5pub struct RdSexpr<'a> {
6    path: RdPath,
7    code: &'a str,
8    options: Option<RdOptionList<'a>>,
9}
10
11impl<'a> RdSexpr<'a> {
12    /// Returns the `\\Sexpr` node path.
13    pub fn path(&self) -> &RdPath {
14        &self.path
15    }
16    /// Returns the R code body.
17    pub fn code(&self) -> &'a str {
18        self.code
19    }
20    /// Returns the parsed local option list, if present.
21    pub fn options(&self) -> Option<&RdOptionList<'a>> {
22        self.options.as_ref()
23    }
24    /// Returns the valid typed local overrides.
25    pub fn option_overrides(&self) -> RdSexprOptionOverrides {
26        self.options
27            .as_ref()
28            .map_or_else(RdSexprOptionOverrides::empty, RdOptionList::typed)
29    }
30}
31
32/// A borrowed, structurally valid `\\RdOpts{options}` view.
33#[derive(Debug, Clone, PartialEq)]
34pub struct RdOpts<'a> {
35    path: RdPath,
36    options: RdOptionList<'a>,
37}
38
39impl<'a> RdOpts<'a> {
40    /// Returns the `\\RdOpts` node path.
41    pub fn path(&self) -> &RdPath {
42        &self.path
43    }
44    /// Returns the parsed option list.
45    pub fn options(&self) -> &RdOptionList<'a> {
46        &self.options
47    }
48    /// Returns the valid typed overrides.
49    pub fn option_overrides(&self) -> RdSexprOptionOverrides {
50        self.options.typed()
51    }
52}
53
54/// The result of resolving one `\\Sexpr` against document-level options.
55#[derive(Debug, Clone, PartialEq)]
56pub struct RdResolvedSexpr<'a> {
57    view: RdSexpr<'a>,
58    effective: RdEffectiveSexprOptions,
59}
60
61impl<'a> RdResolvedSexpr<'a> {
62    /// Returns the inspected expression view.
63    pub fn view(&self) -> &RdSexpr<'a> {
64        &self.view
65    }
66    /// Returns the effective options after local overrides.
67    pub fn effective_options(&self) -> RdEffectiveSexprOptions {
68        self.effective
69    }
70    /// Returns the resolution state.
71    pub fn state(&self) -> RdDynamicMarkupState {
72        RdDynamicMarkupState::Unresolved {
73            stage: self.effective.stage,
74        }
75    }
76}
77
78/// Resolution state for a surviving dynamic markup expression.
79///
80/// There is no `Expanded` variant because lowering validates the producer's
81/// `dynamicFlag` attribute but discards it, so the AST cannot prove that
82/// surrounding content is an evaluated replacement. Every surviving
83/// `\\Sexpr` is unresolved for its effective stage. Renderer fallback policy
84/// (show-as-code, warn-and-omit, or require-pre-expanded) is a downstream
85/// decision made by matching `Unresolved`; `rd-ast` encodes no rendering
86/// policy.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88#[non_exhaustive]
89pub enum RdDynamicMarkupState {
90    /// Not expanded for this evaluation stage.
91    Unresolved { stage: RdSexprStage },
92}
93
94/// An event emitted while resolving dynamic markup in document order.
95#[derive(Debug, Clone, PartialEq)]
96#[non_exhaustive]
97pub enum RdDynamicMarkupEvent<'a> {
98    /// A document-level option update and its resulting state.
99    OptionsChanged {
100        view: RdOpts<'a>,
101        effective: RdEffectiveSexprOptions,
102    },
103    /// A `\\Sexpr` with locally resolved options.
104    Sexpr(RdResolvedSexpr<'a>),
105}
106
107/// A depth-first pre-order dynamic-markup traversal.
108/// The resolver deliberately mirrors R's observable `processRdSexprs`
109/// semantics: one global options state is folded in document order. Conditional
110/// markup (`\\if`/`\\ifelse`) is not branch-selected by this inspection pass,
111/// so an `\\RdOpts` inside an arm that a renderer would not select still
112/// affects subsequent `\\Sexpr` resolution.
113pub struct RdDynamicMarkupIter<'a> {
114    frames: Vec<RdTraversalFrame<'a>>,
115    effective: RdEffectiveSexprOptions,
116}
117
118struct RdTraversalFrame<'a> {
119    nodes: &'a [RdNode],
120    next: usize,
121    path: RdPath,
122    top_level: bool,
123}
124
125impl<'a> Iterator for RdDynamicMarkupIter<'a> {
126    type Item = Result<RdDynamicMarkupEvent<'a>, RdOptionError>;
127    fn next(&mut self) -> Option<Self::Item> {
128        loop {
129            let frame = self.frames.last_mut()?;
130            if frame.next == frame.nodes.len() {
131                self.frames.pop();
132                continue;
133            }
134            let index = frame.next;
135            frame.next += 1;
136            let path = if frame.top_level {
137                RdPath::new(vec![RdPathSegment::TopLevel(index)])
138            } else {
139                frame.path.with_child(index)
140            };
141            let node = &frame.nodes[index];
142            if let Some(tagged) = node.as_tagged() {
143                let result = match tagged.tag() {
144                    RdTag::RdOpts => match tagged.inspect_rd_opts(&path) {
145                        Ok(view) => {
146                            let mut effective = self.effective;
147                            view.option_overrides().apply_to(&mut effective);
148                            self.effective = effective;
149                            Ok(RdDynamicMarkupEvent::OptionsChanged { view, effective })
150                        }
151                        Err(error) => Err(error),
152                    },
153                    RdTag::Sexpr => match tagged.inspect_sexpr(&path) {
154                        Ok(view) => {
155                            let mut effective = self.effective;
156                            view.option_overrides().apply_to(&mut effective);
157                            Ok(RdDynamicMarkupEvent::Sexpr(RdResolvedSexpr {
158                                view,
159                                effective,
160                            }))
161                        }
162                        Err(error) => Err(error),
163                    },
164                    _ => {
165                        self.push_children(tagged.children(), path);
166                        continue;
167                    }
168                };
169                self.push_children(tagged.children(), path);
170                return Some(result);
171            }
172            if let RdNode::Group(group) = node {
173                self.push_children(group.children(), path);
174            }
175        }
176    }
177}
178
179impl<'a> RdDynamicMarkupIter<'a> {
180    fn push_children(&mut self, nodes: &'a [RdNode], path: RdPath) {
181        self.frames.push(RdTraversalFrame {
182            nodes,
183            next: 0,
184            path,
185            top_level: false,
186        });
187    }
188}
189
190impl RdDocument {
191    /// Returns a depth-first pre-order resolver for `\\RdOpts` and `\\Sexpr`.
192    ///
193    /// The resolver deliberately mirrors R's observable `processRdSexprs`
194    /// semantics: a single options state is folded in document order.
195    /// Conditional markup (`\\if`/`\\ifelse`) is not branch-selected here;
196    /// therefore an `\\RdOpts` in an arm that a renderer would not select
197    /// still affects later `\\Sexpr` resolution, matching R's behavior.
198    pub fn inspect_dynamic_markup(&self) -> RdDynamicMarkupIter<'_> {
199        RdDynamicMarkupIter {
200            frames: vec![RdTraversalFrame {
201                nodes: self.nodes(),
202                next: 0,
203                path: RdPath::new(vec![]),
204                top_level: true,
205            }],
206            effective: RdEffectiveSexprOptions::default(),
207        }
208    }
209}
210
211impl RdTagged {
212    /// Strictly inspects a `\\Sexpr{code}` node.
213    pub fn inspect_sexpr<'a>(&'a self, base_path: &RdPath) -> Result<RdSexpr<'a>, RdOptionError> {
214        if self.tag() != &RdTag::Sexpr {
215            return Err(shape(
216                base_path.clone(),
217                Some(self.tag().clone()),
218                RdShapeErrorKind::UnexpectedNode {
219                    expected: RdExpectedNode::Sexpr,
220                    actual: RdNodeKind::Tagged,
221                },
222            )
223            .into());
224        }
225        let children = self.children();
226        if children.len() != 1 {
227            return Err(shape(
228                base_path.clone(),
229                Some(RdTag::Sexpr),
230                RdShapeErrorKind::WrongArity {
231                    expected: RdArity::Exactly(1),
232                    actual: children.len(),
233                },
234            )
235            .into());
236        }
237        let code = match &children[0] {
238            RdNode::RCode(code) => code.as_str(),
239            node => {
240                return Err(shape(
241                    base_path.with_child(0),
242                    Some(RdTag::Sexpr),
243                    RdShapeErrorKind::UnexpectedContent {
244                        actual: RdNodeKind::of(node),
245                    },
246                )
247                .into());
248            }
249        };
250        let options = self
251            .option()
252            .map(|nodes| RdOptionList::parse(nodes, base_path.with_option()))
253            .transpose()?;
254        Ok(RdSexpr {
255            path: base_path.clone(),
256            code,
257            options,
258        })
259    }
260
261    /// Strictly inspects a `\\RdOpts{options}` node.
262    pub fn inspect_rd_opts<'a>(&'a self, base_path: &RdPath) -> Result<RdOpts<'a>, RdOptionError> {
263        if self.tag() != &RdTag::RdOpts {
264            return Err(shape(
265                base_path.clone(),
266                Some(self.tag().clone()),
267                RdShapeErrorKind::UnexpectedNode {
268                    expected: RdExpectedNode::RdOpts,
269                    actual: RdNodeKind::Tagged,
270                },
271            )
272            .into());
273        }
274        if self.option().is_some() {
275            return Err(shape(
276                base_path.clone(),
277                Some(RdTag::RdOpts),
278                RdShapeErrorKind::UnexpectedOption,
279            )
280            .into());
281        }
282        let options = RdOptionList::parse(self.children(), base_path.clone())?;
283        Ok(RdOpts {
284            path: base_path.clone(),
285            options,
286        })
287    }
288}