ass_core/parser/sections/script_info/
parser.rs1use crate::{
8 parser::{
9 ast::{ScriptInfo, Section},
10 errors::{IssueCategory, IssueSeverity, ParseIssue},
11 position_tracker::PositionTracker,
12 sections::ScriptInfoParseResult,
13 ParseResult,
14 },
15 ScriptVersion,
16};
17use alloc::vec::Vec;
18
19pub struct ScriptInfoParser<'a> {
30 tracker: PositionTracker<'a>,
32 issues: Vec<ParseIssue>,
34}
35
36impl<'a> ScriptInfoParser<'a> {
37 #[must_use]
45 #[allow(clippy::missing_const_for_fn)] pub fn new(source: &'a str, start_position: usize, start_line: usize) -> Self {
47 Self {
48 tracker: PositionTracker::new_at(
49 source,
50 start_position,
51 u32::try_from(start_line).unwrap_or(u32::MAX),
52 1,
53 ),
54 issues: Vec::new(),
55 }
56 }
57
58 pub fn parse(mut self) -> ParseResult<ScriptInfoParseResult<'a>> {
72 let section_start = self.tracker.checkpoint();
73 let mut fields = Vec::new();
74 let mut detected_version = None;
75
76 while !self.tracker.is_at_end() && !self.at_next_section() {
77 self.skip_whitespace_and_comments();
78
79 if self.tracker.is_at_end() || self.at_next_section() {
80 break;
81 }
82
83 let line_start = self.tracker.checkpoint();
84 let line = self.current_line().trim();
85
86 if line.is_empty() {
87 self.tracker.skip_line();
88 continue;
89 }
90
91 if let Some(colon_pos) = line.find(':') {
92 let key = line[..colon_pos].trim();
93 let value = line[colon_pos + 1..].trim();
94
95 if key == "ScriptType" {
96 if let Some(version) = ScriptVersion::from_header(value) {
97 detected_version = Some(version);
98 }
99 }
100
101 fields.push((key, value));
102 } else {
103 self.issues.push(ParseIssue::new(
104 IssueSeverity::Warning,
105 IssueCategory::Format,
106 "Invalid script info line format".into(),
107 line_start.line() as usize,
108 ));
109 }
110
111 self.tracker.skip_line();
112 }
113
114 let span = self.tracker.span_from(§ion_start);
115 let section = Section::ScriptInfo(ScriptInfo { fields, span });
116
117 Ok((
118 section,
119 detected_version,
120 self.issues,
121 self.tracker.offset(),
122 self.tracker.line() as usize,
123 ))
124 }
125
126 fn current_line(&self) -> &'a str {
128 let remaining = self.tracker.remaining();
129 let end = remaining.find('\n').unwrap_or(remaining.len());
130 &remaining[..end]
131 }
132
133 fn at_next_section(&self) -> bool {
135 self.tracker.remaining().trim_start().starts_with('[')
136 }
137
138 fn skip_whitespace_and_comments(&mut self) {
140 loop {
141 self.tracker.skip_whitespace();
142
143 let remaining = self.tracker.remaining();
144 if remaining.is_empty() {
145 break;
146 }
147
148 if remaining.starts_with(';') || remaining.starts_with('#') {
149 self.tracker.skip_line();
150 continue;
151 }
152
153 if remaining.starts_with('\n') {
155 self.tracker.advance(1);
156 continue;
157 }
158
159 break;
160 }
161 }
162
163 #[must_use]
165 pub fn issues(self) -> Vec<ParseIssue> {
166 self.issues
167 }
168}