1use alloc::string::String;
12use alloc::vec::Vec;
13
14use brink_format::{LineEntry, PluralResolver};
15
16use super::{OutputBuffer, OutputPart, resolve_parts};
17use crate::program::Program;
18
19#[derive(Debug, Clone, PartialEq)]
21pub struct Fragment {
22 pub parts: Vec<OutputPart>,
23 pub tags: Vec<String>,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq)]
28pub struct FragmentRef<'a> {
29 pub parts: &'a [OutputPart],
30 pub tags: &'a [String],
31}
32
33#[derive(Debug, Clone, Default)]
35struct FragmentSpan {
36 start: u32,
37 end: u32,
38 tags: Vec<String>,
39}
40
41#[derive(Debug, Clone, Default)]
59pub struct Fragments {
60 arena: Vec<OutputPart>,
61 spans: Vec<FragmentSpan>,
62}
63
64impl Fragments {
65 #[must_use]
67 pub fn len(&self) -> usize {
68 self.spans.len()
69 }
70
71 #[must_use]
73 pub fn is_empty(&self) -> bool {
74 self.spans.is_empty()
75 }
76
77 #[must_use]
79 pub fn parts(&self, idx: u32) -> Option<&[OutputPart]> {
80 let span = self.spans.get(idx as usize)?;
81 self.arena.get(span.start as usize..span.end as usize)
82 }
83
84 #[must_use]
86 pub fn tags(&self, idx: u32) -> Option<&[String]> {
87 self.spans.get(idx as usize).map(|s| s.tags.as_slice())
88 }
89
90 #[must_use]
92 pub fn get(&self, idx: u32) -> Option<FragmentRef<'_>> {
93 Some(FragmentRef {
94 parts: self.parts(idx)?,
95 tags: self.tags(idx)?,
96 })
97 }
98
99 pub fn iter(&self) -> impl ExactSizeIterator<Item = FragmentRef<'_>> + '_ {
101 self.spans.iter().map(|span| FragmentRef {
102 parts: &self.arena[span.start as usize..span.end as usize],
103 tags: &span.tags,
104 })
105 }
106
107 #[must_use]
109 pub fn to_vec(&self) -> Vec<Fragment> {
110 self.iter()
111 .map(|f| Fragment {
112 parts: f.parts.to_vec(),
113 tags: f.tags.to_vec(),
114 })
115 .collect()
116 }
117
118 #[expect(clippy::cast_possible_truncation)]
120 pub(crate) fn push(
121 &mut self,
122 parts: impl Iterator<Item = OutputPart>,
123 tags: Vec<String>,
124 ) -> u32 {
125 let start = self.arena.len() as u32;
126 self.arena.extend(parts);
127 let end = self.arena.len() as u32;
128 let idx = self.spans.len() as u32;
129 self.spans.push(FragmentSpan { start, end, tags });
130 idx
131 }
132}
133
134impl From<Vec<Fragment>> for Fragments {
135 fn from(fragments: Vec<Fragment>) -> Self {
136 let mut store = Self::default();
137 for Fragment { parts, tags } in fragments {
138 store.push(parts.into_iter(), tags);
139 }
140 store
141 }
142}
143
144impl OutputBuffer {
145 pub fn begin_fragment(&mut self) {
149 self.fragment_depth += 1;
150 self.fragment_capture.push(OutputPart::Checkpoint);
151 self.fragment_pending_tags.push(Vec::new());
152 }
153
154 pub fn end_fragment(&mut self) -> Option<u32> {
157 let cp_idx = self
158 .fragment_capture
159 .iter()
160 .rposition(|p| matches!(p, OutputPart::Checkpoint))?;
161
162 let tags = self.fragment_pending_tags.pop().unwrap_or_default();
163 let idx = self
167 .fragments
168 .push(self.fragment_capture.drain(cp_idx + 1..), tags);
169 self.fragment_capture.pop();
170
171 self.fragment_depth = self.fragment_depth.saturating_sub(1);
172
173 Some(idx)
174 }
175
176 pub fn in_fragment_capture(&self) -> bool {
178 self.fragment_depth > 0
179 }
180
181 pub fn push_fragment_tag(&mut self, tag: String) {
183 if let Some(pending) = self.fragment_pending_tags.last_mut() {
184 pending.push(tag);
185 }
186 }
187
188 pub fn fragment_tags(&self, idx: u32) -> Option<&[String]> {
190 self.fragments.tags(idx)
191 }
192
193 pub fn fragments(&self) -> &Fragments {
195 &self.fragments
196 }
197
198 pub fn fragment(&self, idx: u32) -> Option<&[OutputPart]> {
200 self.fragments.parts(idx)
201 }
202
203 pub fn fragment_source(
208 &self,
209 idx: u32,
210 program: &Program,
211 line_tables: &[Vec<LineEntry>],
212 ) -> Option<brink_format::SourceLocation> {
213 self.fragment(idx)?.iter().find_map(|part| match part {
214 OutputPart::LineRef {
215 container_idx,
216 line_idx,
217 ..
218 } => {
219 let scope_idx = program.scope_table_idx(*container_idx) as usize;
220 line_tables
221 .get(scope_idx)
222 .and_then(|t| t.get(*line_idx as usize))
223 .and_then(|entry| entry.source_location.clone())
224 }
225 _ => None,
226 })
227 }
228
229 pub fn resolve_fragment(
231 &self,
232 idx: u32,
233 program: &Program,
234 line_tables: &[Vec<LineEntry>],
235 resolver: Option<&dyn PluralResolver>,
236 ) -> String {
237 match self.fragment(idx) {
238 Some(parts) => resolve_parts(parts, program, line_tables, resolver, &self.fragments),
239 None => String::new(),
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use alloc::string::ToString;
247 use alloc::vec;
248
249 use super::*;
250
251 fn text(s: &str) -> OutputPart {
252 OutputPart::Text(s.to_string())
253 }
254
255 #[test]
260 fn nested_captures_share_the_arena_with_disjoint_spans() {
261 let mut buf = OutputBuffer::new();
262 buf.begin_fragment();
263 buf.push_text("outer-a");
264 buf.begin_fragment();
265 buf.push_text("inner");
266 buf.push_fragment_tag("t".to_string());
267 let inner = buf.end_fragment().expect("inner capture open");
268 buf.push_text("outer-b");
269 let outer = buf.end_fragment().expect("outer capture open");
270
271 assert_eq!((inner, outer), (0, 1));
272 assert_eq!(buf.fragments().len(), 2);
273 assert_eq!(buf.fragment(inner), Some(&[text("inner")][..]));
274 assert_eq!(buf.fragment_tags(inner), Some(&["t".to_string()][..]));
275 assert_eq!(
276 buf.fragment(outer),
277 Some(&[text("outer-a"), text("outer-b")][..])
278 );
279 assert_eq!(buf.fragment_tags(outer), Some(&[][..]));
280 assert!(buf.fragment(2).is_none());
281 assert!(!buf.in_fragment_capture());
282 }
283
284 #[test]
287 fn owning_form_round_trips_through_the_store() {
288 let owned = vec![
289 Fragment {
290 parts: vec![text("a"), OutputPart::Newline],
291 tags: vec!["x".to_string()],
292 },
293 Fragment {
294 parts: vec![],
295 tags: vec![],
296 },
297 Fragment {
298 parts: vec![OutputPart::Spring, text("c")],
299 tags: vec!["y".to_string(), "z".to_string()],
300 },
301 ];
302 let store = Fragments::from(owned.clone());
303 assert_eq!(store.len(), 3);
304 assert_eq!(store.parts(1), Some(&[][..]));
305 assert_eq!(store.get(2).map(|f| f.tags.len()), Some(2));
306 assert_eq!(store.to_vec(), owned);
307 assert_eq!(store.iter().count(), 3);
308 }
309}