ass_core/parser/streaming/
delta.rs

1//! Parse delta operations for streaming updates
2//!
3//! Provides delta tracking for efficient incremental parsing and editor
4//! integration. Deltas represent minimal changes between parsing states.
5
6use crate::parser::ast::Section;
7use alloc::{string::String, vec::Vec};
8
9/// Delta operations for streaming updates
10///
11/// Represents atomic changes detected during incremental parsing.
12/// Used by editors and streaming applications to efficiently update
13/// internal state without full re-parsing.
14///
15/// # Performance
16///
17/// Deltas use zero-copy design where possible to minimize allocations.
18/// Section references point to parsed AST nodes using lifetime parameters.
19///
20/// # Example
21///
22/// ```rust
23/// use ass_core::parser::streaming::ParseDelta;
24///
25/// // Handle delta operations
26/// fn apply_deltas(deltas: Vec<ParseDelta>) {
27///     for delta in deltas {
28///         match delta {
29///             ParseDelta::AddSection(section) => {
30///                 // Add new section to document
31///             }
32///             ParseDelta::UpdateSection(index, section) => {
33///                 // Update existing section
34///             }
35///             ParseDelta::RemoveSection(index) => {
36///                 // Remove section at index
37///             }
38///             ParseDelta::ParseIssue(issue) => {
39///                 // Handle parsing error/warning
40///             }
41///         }
42///     }
43/// }
44/// ```
45#[derive(Debug, Clone)]
46pub enum ParseDelta<'a> {
47    /// Section was added to the script
48    ///
49    /// Contains the complete parsed section with zero-copy references
50    /// to source text spans.
51    AddSection(Section<'a>),
52
53    /// Section was modified during incremental parsing
54    ///
55    /// Contains the section index and updated section data. Consumers should replace
56    /// the existing section at the specified index with this new data.
57    UpdateSection(usize, Section<'a>),
58
59    /// Section was removed by index
60    ///
61    /// Contains the zero-based index of the section that should be
62    /// removed from the script.
63    RemoveSection(usize),
64
65    /// Parsing issue detected during processing
66    ///
67    /// Contains error or warning message about parsing problems.
68    /// Parsing may continue despite issues for error recovery.
69    ParseIssue(String),
70}
71
72impl<'a> ParseDelta<'a> {
73    /// Create delta for adding a section
74    #[must_use]
75    pub const fn add_section(section: Section<'a>) -> Self {
76        Self::AddSection(section)
77    }
78
79    /// Create delta for updating a section
80    #[must_use]
81    pub const fn update_section(index: usize, section: Section<'a>) -> Self {
82        Self::UpdateSection(index, section)
83    }
84
85    /// Create delta for removing a section by index
86    #[must_use]
87    pub const fn remove_section(index: usize) -> Self {
88        Self::RemoveSection(index)
89    }
90
91    /// Create delta for parsing issue
92    #[must_use]
93    pub const fn parse_issue(message: String) -> Self {
94        Self::ParseIssue(message)
95    }
96
97    /// Check if delta represents an error condition
98    #[must_use]
99    pub const fn is_error(&self) -> bool {
100        matches!(self, Self::ParseIssue(_))
101    }
102
103    /// Check if delta modifies document structure
104    #[must_use]
105    pub const fn is_structural(&self) -> bool {
106        matches!(
107            self,
108            Self::AddSection(_) | Self::UpdateSection(_, _) | Self::RemoveSection(_)
109        )
110    }
111
112    /// Get section reference if delta contains one
113    #[must_use]
114    pub const fn section(&self) -> Option<&Section<'a>> {
115        match self {
116            Self::AddSection(section) | Self::UpdateSection(_, section) => Some(section),
117            _ => None,
118        }
119    }
120}
121
122/// Collection of parse deltas with batch operations
123///
124/// Provides utilities for working with multiple deltas efficiently,
125/// including filtering, merging, and validation.
126#[derive(Debug, Clone)]
127pub struct DeltaBatch<'a> {
128    /// Collection of deltas representing changes to the script
129    deltas: Vec<ParseDelta<'a>>,
130}
131
132impl<'a> DeltaBatch<'a> {
133    /// Create new empty delta batch
134    #[must_use]
135    pub const fn new() -> Self {
136        Self { deltas: Vec::new() }
137    }
138
139    /// Create batch from existing deltas
140    #[must_use]
141    pub const fn from_deltas(deltas: Vec<ParseDelta<'a>>) -> Self {
142        Self { deltas }
143    }
144
145    /// Add delta to batch
146    pub fn push(&mut self, delta: ParseDelta<'a>) {
147        self.deltas.push(delta);
148    }
149
150    /// Extend batch with multiple deltas
151    pub fn extend(&mut self, other_deltas: impl IntoIterator<Item = ParseDelta<'a>>) {
152        self.deltas.extend(other_deltas);
153    }
154
155    /// Get all deltas
156    #[must_use]
157    #[allow(clippy::missing_const_for_fn)]
158    pub fn deltas(&self) -> &[ParseDelta<'a>] {
159        &self.deltas
160    }
161
162    /// Convert to vector of deltas
163    #[must_use]
164    pub fn into_deltas(self) -> Vec<ParseDelta<'a>> {
165        self.deltas
166    }
167
168    /// Check if batch is empty
169    #[must_use]
170    pub fn is_empty(&self) -> bool {
171        self.deltas.is_empty()
172    }
173
174    /// Get number of deltas in batch
175    #[must_use]
176    pub fn len(&self) -> usize {
177        self.deltas.len()
178    }
179
180    /// Filter deltas by predicate
181    #[must_use]
182    pub fn filter<F>(&self, predicate: F) -> Self
183    where
184        F: Fn(&ParseDelta<'a>) -> bool,
185    {
186        let filtered = self
187            .deltas
188            .iter()
189            .filter(|d| predicate(d))
190            .cloned()
191            .collect();
192        DeltaBatch::from_deltas(filtered)
193    }
194
195    /// Get only structural deltas (add/update/remove)
196    #[must_use]
197    pub fn structural_only(&self) -> Self {
198        self.filter(ParseDelta::is_structural)
199    }
200
201    /// Get only error deltas
202    #[must_use]
203    pub fn errors_only(&self) -> Self {
204        self.filter(ParseDelta::is_error)
205    }
206
207    /// Check if batch contains any errors
208    pub fn has_errors(&self) -> bool {
209        self.deltas.iter().any(ParseDelta::is_error)
210    }
211}
212
213impl Default for DeltaBatch<'_> {
214    fn default() -> Self {
215        Self::new()
216    }
217}
218
219impl<'a> FromIterator<ParseDelta<'a>> for DeltaBatch<'a> {
220    fn from_iter<T: IntoIterator<Item = ParseDelta<'a>>>(iter: T) -> Self {
221        Self::from_deltas(iter.into_iter().collect())
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use crate::parser::ast::{ScriptInfo, Span};
229    #[cfg(not(feature = "std"))]
230    use alloc::{format, string::ToString, vec};
231
232    #[test]
233    fn delta_creation() {
234        let section = Section::ScriptInfo(ScriptInfo {
235            fields: vec![],
236            span: Span::new(0, 0, 0, 0),
237        });
238        let delta = ParseDelta::add_section(section);
239        assert!(matches!(delta, ParseDelta::AddSection(_)));
240        assert!(!delta.is_error());
241        assert!(delta.is_structural());
242    }
243
244    #[test]
245    fn delta_properties() {
246        let remove_delta = ParseDelta::remove_section(5);
247        assert!(remove_delta.is_structural());
248        assert!(!remove_delta.is_error());
249        assert_eq!(remove_delta.section(), None);
250
251        let error_delta = ParseDelta::parse_issue("Test error".to_string());
252        assert!(!error_delta.is_structural());
253        assert!(error_delta.is_error());
254    }
255
256    #[test]
257    fn delta_batch_operations() {
258        let mut batch = DeltaBatch::new();
259        assert!(batch.is_empty());
260        assert_eq!(batch.len(), 0);
261
262        let section = Section::ScriptInfo(ScriptInfo {
263            fields: vec![],
264            span: Span::new(0, 0, 0, 0),
265        });
266        batch.push(ParseDelta::add_section(section));
267        batch.push(ParseDelta::parse_issue("Warning".to_string()));
268
269        assert!(!batch.is_empty());
270        assert_eq!(batch.len(), 2);
271        assert!(batch.has_errors());
272
273        let structural = batch.structural_only();
274        assert_eq!(structural.len(), 1);
275        assert!(!structural.has_errors());
276
277        let errors = batch.errors_only();
278        assert_eq!(errors.len(), 1);
279        assert!(errors.has_errors());
280    }
281
282    #[test]
283    fn batch_from_iterator() {
284        let deltas = vec![
285            ParseDelta::remove_section(0),
286            ParseDelta::parse_issue("Error".to_string()),
287        ];
288
289        let batch: DeltaBatch = deltas.into_iter().collect();
290        assert_eq!(batch.len(), 2);
291    }
292
293    #[test]
294    fn delta_update_section() {
295        let section = Section::ScriptInfo(ScriptInfo {
296            fields: vec![],
297            span: Span::new(0, 0, 0, 0),
298        });
299        let delta = ParseDelta::update_section(3, section);
300
301        assert!(matches!(delta, ParseDelta::UpdateSection(3, _)));
302        assert!(!delta.is_error());
303        assert!(delta.is_structural());
304        assert!(delta.section().is_some());
305    }
306
307    #[test]
308    fn delta_section_getter() {
309        let section = Section::ScriptInfo(ScriptInfo {
310            fields: vec![],
311            span: Span::new(0, 0, 0, 0),
312        });
313
314        let add_delta = ParseDelta::add_section(section.clone());
315        assert!(add_delta.section().is_some());
316
317        let update_delta = ParseDelta::update_section(0, section);
318        assert!(update_delta.section().is_some());
319
320        let remove_delta = ParseDelta::remove_section(1);
321        assert!(remove_delta.section().is_none());
322
323        let error_delta = ParseDelta::parse_issue("Test".to_string());
324        assert!(error_delta.section().is_none());
325    }
326
327    #[test]
328    fn delta_debug_formatting() {
329        let section = Section::ScriptInfo(ScriptInfo {
330            fields: vec![],
331            span: Span::new(0, 0, 0, 0),
332        });
333        let delta = ParseDelta::add_section(section);
334        let debug_str = format!("{delta:?}");
335        assert!(debug_str.contains("AddSection"));
336
337        let error_delta = ParseDelta::parse_issue("Error message".to_string());
338        let error_debug = format!("{error_delta:?}");
339        assert!(error_debug.contains("ParseIssue"));
340        assert!(error_debug.contains("Error message"));
341    }
342
343    #[test]
344    fn delta_clone() {
345        let section = Section::ScriptInfo(ScriptInfo {
346            fields: vec![],
347            span: Span::new(0, 0, 0, 0),
348        });
349        let delta = ParseDelta::add_section(section);
350        let cloned = delta.clone();
351
352        assert!(matches!(cloned, ParseDelta::AddSection(_)));
353        assert_eq!(delta.is_error(), cloned.is_error());
354        assert_eq!(delta.is_structural(), cloned.is_structural());
355    }
356
357    #[test]
358    fn delta_all_constructors() {
359        let section = Section::ScriptInfo(ScriptInfo {
360            fields: vec![],
361            span: Span::new(0, 0, 0, 0),
362        });
363
364        let add = ParseDelta::add_section(section.clone());
365        assert!(matches!(add, ParseDelta::AddSection(_)));
366
367        let update = ParseDelta::update_section(42, section);
368        assert!(matches!(update, ParseDelta::UpdateSection(42, _)));
369
370        let remove = ParseDelta::remove_section(99);
371        assert!(matches!(remove, ParseDelta::RemoveSection(99)));
372
373        let issue = ParseDelta::parse_issue("Test issue".to_string());
374        assert!(matches!(issue, ParseDelta::ParseIssue(_)));
375    }
376
377    #[test]
378    fn batch_default() {
379        let batch = DeltaBatch::default();
380        assert!(batch.is_empty());
381        assert_eq!(batch.len(), 0);
382        assert!(!batch.has_errors());
383    }
384
385    #[test]
386    fn batch_debug_and_clone() {
387        let section = Section::ScriptInfo(ScriptInfo {
388            fields: vec![],
389            span: Span::new(0, 0, 0, 0),
390        });
391        let mut batch = DeltaBatch::new();
392        batch.push(ParseDelta::add_section(section));
393
394        let debug_str = format!("{batch:?}");
395        assert!(debug_str.contains("DeltaBatch"));
396
397        let cloned = batch.clone();
398        assert_eq!(batch.len(), cloned.len());
399        assert_eq!(batch.is_empty(), cloned.is_empty());
400    }
401
402    #[test]
403    fn batch_extend_operations() {
404        let mut batch = DeltaBatch::new();
405        let section1 = Section::ScriptInfo(ScriptInfo {
406            fields: vec![],
407            span: Span::new(0, 0, 0, 0),
408        });
409        let section2 = Section::ScriptInfo(ScriptInfo {
410            fields: vec![],
411            span: Span::new(0, 0, 0, 0),
412        });
413
414        let deltas = vec![
415            ParseDelta::add_section(section1),
416            ParseDelta::update_section(0, section2),
417            ParseDelta::remove_section(1),
418        ];
419
420        batch.extend(deltas);
421        assert_eq!(batch.len(), 3);
422        assert!(!batch.has_errors());
423    }
424
425    #[test]
426    fn batch_from_deltas() {
427        let section = Section::ScriptInfo(ScriptInfo {
428            fields: vec![],
429            span: Span::new(0, 0, 0, 0),
430        });
431        let deltas = vec![
432            ParseDelta::add_section(section),
433            ParseDelta::parse_issue("Warning".to_string()),
434        ];
435
436        let batch = DeltaBatch::from_deltas(deltas);
437        assert_eq!(batch.len(), 2);
438        assert!(batch.has_errors());
439    }
440
441    #[test]
442    fn batch_into_deltas() {
443        let section = Section::ScriptInfo(ScriptInfo {
444            fields: vec![],
445            span: Span::new(0, 0, 0, 0),
446        });
447        let mut batch = DeltaBatch::new();
448        batch.push(ParseDelta::add_section(section));
449        batch.push(ParseDelta::remove_section(0));
450
451        let deltas = batch.into_deltas();
452        assert_eq!(deltas.len(), 2);
453    }
454
455    #[test]
456    fn batch_complex_filtering() {
457        let section1 = Section::ScriptInfo(ScriptInfo {
458            fields: vec![],
459            span: Span::new(0, 0, 0, 0),
460        });
461        let section2 = Section::ScriptInfo(ScriptInfo {
462            fields: vec![],
463            span: Span::new(0, 0, 0, 0),
464        });
465        let mut batch = DeltaBatch::new();
466
467        batch.push(ParseDelta::add_section(section1));
468        batch.push(ParseDelta::update_section(0, section2));
469        batch.push(ParseDelta::remove_section(1));
470        batch.push(ParseDelta::parse_issue("Error 1".to_string()));
471        batch.push(ParseDelta::parse_issue("Error 2".to_string()));
472
473        assert_eq!(batch.len(), 5);
474
475        let structural = batch.structural_only();
476        assert_eq!(structural.len(), 3);
477        assert!(!structural.has_errors());
478
479        let errors = batch.errors_only();
480        assert_eq!(errors.len(), 2);
481        assert!(errors.has_errors());
482
483        // Custom filter
484        let only_adds = batch.filter(|delta| matches!(delta, ParseDelta::AddSection(_)));
485        assert_eq!(only_adds.len(), 1);
486    }
487
488    #[test]
489    fn batch_empty_operations() {
490        let batch = DeltaBatch::new();
491
492        let structural = batch.structural_only();
493        assert!(structural.is_empty());
494
495        let errors = batch.errors_only();
496        assert!(errors.is_empty());
497
498        assert!(!batch.has_errors());
499        assert_eq!(batch.deltas().len(), 0);
500    }
501
502    #[test]
503    fn delta_all_variants_coverage() {
504        // Test all ParseDelta variants
505        let section = Section::ScriptInfo(ScriptInfo {
506            fields: vec![],
507            span: Span::new(0, 0, 0, 0),
508        });
509
510        // AddSection
511        let add = ParseDelta::AddSection(section.clone());
512        assert!(add.is_structural());
513        assert!(!add.is_error());
514        assert!(add.section().is_some());
515
516        // UpdateSection
517        let update = ParseDelta::UpdateSection(5, section);
518        assert!(update.is_structural());
519        assert!(!update.is_error());
520        assert!(update.section().is_some());
521
522        // RemoveSection
523        let remove = ParseDelta::RemoveSection(10);
524        assert!(remove.is_structural());
525        assert!(!remove.is_error());
526        assert!(remove.section().is_none());
527
528        // ParseIssue
529        let issue = ParseDelta::ParseIssue("Critical error".to_string());
530        assert!(!issue.is_structural());
531        assert!(issue.is_error());
532        assert!(issue.section().is_none());
533    }
534
535    #[test]
536    fn batch_iterator_trait() {
537        let section = Section::ScriptInfo(ScriptInfo {
538            fields: vec![],
539            span: Span::new(0, 0, 0, 0),
540        });
541        let deltas = [
542            ParseDelta::add_section(section),
543            ParseDelta::remove_section(0),
544            ParseDelta::parse_issue("Test".to_string()),
545        ];
546
547        let batch: DeltaBatch = deltas.into_iter().collect();
548        assert_eq!(batch.len(), 3);
549
550        // Test that we can collect from any iterator
551        let filtered_deltas = batch
552            .deltas()
553            .iter()
554            .filter(|&d| d.is_structural())
555            .cloned();
556        let filtered_batch: DeltaBatch = filtered_deltas.collect();
557        assert_eq!(filtered_batch.len(), 2);
558    }
559}