use crate::{
parser::{
ast::{ScriptInfo, Section},
errors::{IssueCategory, IssueSeverity, ParseIssue},
position_tracker::PositionTracker,
sections::ScriptInfoParseResult,
ParseResult,
},
ScriptVersion,
};
use alloc::vec::Vec;
pub struct ScriptInfoParser<'a> {
tracker: PositionTracker<'a>,
issues: Vec<ParseIssue>,
}
impl<'a> ScriptInfoParser<'a> {
#[must_use]
#[allow(clippy::missing_const_for_fn)] pub fn new(source: &'a str, start_position: usize, start_line: usize) -> Self {
Self {
tracker: PositionTracker::new_at(
source,
start_position,
u32::try_from(start_line).unwrap_or(u32::MAX),
1,
),
issues: Vec::new(),
}
}
pub fn parse(mut self) -> ParseResult<ScriptInfoParseResult<'a>> {
let section_start = self.tracker.checkpoint();
let mut fields = Vec::new();
let mut detected_version = None;
while !self.tracker.is_at_end() && !self.at_next_section() {
self.skip_whitespace_and_comments();
if self.tracker.is_at_end() || self.at_next_section() {
break;
}
let line_start = self.tracker.checkpoint();
let line = self.current_line().trim();
if line.is_empty() {
self.tracker.skip_line();
continue;
}
if let Some(colon_pos) = line.find(':') {
let key = line[..colon_pos].trim();
let value = line[colon_pos + 1..].trim();
if key == "ScriptType" {
if let Some(version) = ScriptVersion::from_header(value) {
detected_version = Some(version);
}
}
fields.push((key, value));
} else {
self.issues.push(ParseIssue::new(
IssueSeverity::Warning,
IssueCategory::Format,
"Invalid script info line format".into(),
line_start.line() as usize,
));
}
self.tracker.skip_line();
}
let span = self.tracker.span_from(§ion_start);
let section = Section::ScriptInfo(ScriptInfo { fields, span });
Ok((
section,
detected_version,
self.issues,
self.tracker.offset(),
self.tracker.line() as usize,
))
}
fn current_line(&self) -> &'a str {
let remaining = self.tracker.remaining();
let end = remaining.find('\n').unwrap_or(remaining.len());
&remaining[..end]
}
fn at_next_section(&self) -> bool {
self.tracker.remaining().trim_start().starts_with('[')
}
fn skip_whitespace_and_comments(&mut self) {
loop {
self.tracker.skip_whitespace();
let remaining = self.tracker.remaining();
if remaining.is_empty() {
break;
}
if remaining.starts_with(';') || remaining.starts_with('#') {
self.tracker.skip_line();
continue;
}
if remaining.starts_with('\n') {
self.tracker.advance(1);
continue;
}
break;
}
}
#[must_use]
pub fn issues(self) -> Vec<ParseIssue> {
self.issues
}
}