Skip to main content

ass_core/parser/sections/events/
parser.rs

1//! Streaming parse lifecycle for the `[Events]` section.
2//!
3//! Implements [`EventsParser`] construction, the top-level parse loop, format
4//! line handling, and the whitespace/comment control helpers used while
5//! scanning event entries.
6
7use super::EventsParser;
8use crate::parser::{
9    ast::Section, errors::ParseIssue, position_tracker::PositionTracker,
10    sections::SectionParseResult, ParseResult,
11};
12use alloc::vec::Vec;
13
14impl<'a> EventsParser<'a> {
15    /// Create new events parser for source text
16    ///
17    /// # Arguments
18    ///
19    /// * `source` - Source text to parse
20    /// * `start_position` - Starting byte position in source
21    /// * `start_line` - Starting line number for error reporting
22    #[must_use]
23    #[allow(clippy::missing_const_for_fn)] // Can't be const due to Vec::new()
24    pub fn new(source: &'a str, start_position: usize, start_line: usize) -> Self {
25        Self {
26            tracker: PositionTracker::new_at(
27                source,
28                start_position,
29                u32::try_from(start_line).unwrap_or(u32::MAX),
30                1,
31            ),
32            issues: Vec::new(),
33            format: None,
34        }
35    }
36
37    /// Create a new parser with a pre-known format for incremental parsing
38    #[must_use]
39    pub fn with_format(
40        source: &'a str,
41        format: &[&'a str],
42        start_position: usize,
43        start_line: u32,
44    ) -> Self {
45        Self {
46            tracker: PositionTracker::new_at(source, start_position, start_line, 1),
47            issues: Vec::new(),
48            format: Some(format.to_vec()),
49        }
50    }
51
52    /// Parse events section content
53    ///
54    /// Returns the parsed section and any issues encountered during parsing.
55    /// Handles Format line parsing and event entry validation.
56    ///
57    /// # Returns
58    ///
59    /// Tuple of (`parsed_section`, `format_fields`, `parse_issues`, `final_position`, `final_line`)
60    ///
61    /// # Errors
62    ///
63    /// Returns an error if the events section contains malformed format lines or
64    /// other unrecoverable syntax errors.
65    pub fn parse(mut self) -> ParseResult<SectionParseResult<'a>> {
66        let mut events = Vec::new();
67
68        while !self.tracker.is_at_end() && !self.at_next_section() {
69            self.skip_whitespace_and_comments();
70
71            if self.tracker.is_at_end() || self.at_next_section() {
72                break;
73            }
74
75            let line_start = self.tracker.checkpoint();
76            let line = self.current_line().trim();
77
78            if line.is_empty() {
79                self.tracker.skip_line();
80                continue;
81            }
82
83            if line.starts_with("Format:") {
84                self.parse_format_line(line);
85            } else if let Some(event) = self.parse_event_line_internal(line, &line_start) {
86                events.push(event);
87            }
88
89            self.tracker.skip_line();
90        }
91
92        Ok((
93            Section::Events(events),
94            self.format,
95            self.issues,
96            self.tracker.offset(),
97            self.tracker.line() as usize,
98        ))
99    }
100
101    /// Parse format specification line
102    fn parse_format_line(&mut self, line: &'a str) {
103        if let Some(format_data) = line.strip_prefix("Format:") {
104            let fields: Vec<&'a str> = format_data.split(',').map(str::trim).collect();
105            self.format = Some(fields);
106        }
107    }
108
109    /// Get current line from source
110    pub(super) fn current_line(&self) -> &'a str {
111        let remaining = self.tracker.remaining();
112        let end = remaining.find('\n').unwrap_or(remaining.len());
113        &remaining[..end]
114    }
115
116    /// Check if at start of next section
117    #[must_use]
118    fn at_next_section(&self) -> bool {
119        self.tracker.remaining().trim_start().starts_with('[')
120    }
121
122    /// Skip whitespace and comment lines
123    fn skip_whitespace_and_comments(&mut self) {
124        loop {
125            self.tracker.skip_whitespace();
126
127            let remaining = self.tracker.remaining();
128            if remaining.is_empty() {
129                break;
130            }
131
132            if remaining.starts_with(';') || remaining.starts_with('#') {
133                self.tracker.skip_line();
134                continue;
135            }
136
137            // Check for newlines in whitespace
138            if remaining.starts_with('\n') {
139                self.tracker.advance(1);
140                continue;
141            }
142
143            break;
144        }
145    }
146
147    /// Get accumulated parse issues
148    #[must_use]
149    pub fn issues(self) -> Vec<ParseIssue> {
150        self.issues
151    }
152
153    /// Get format specification
154    #[must_use]
155    pub const fn format(&self) -> Option<&Vec<&'a str>> {
156        self.format.as_ref()
157    }
158}