use std::{cell::RefCell, rc::Rc};
use super::{
super::prompt::{DefDuplication, Prompter},
ProcessContext, TokenProcessor, parse_obj_ids,
};
use crate::bms::ParseErrorWithRange;
use crate::{
bms::{
model::{StringValue, stop::StopObjects},
parse::{ParseWarning, Result},
prelude::*,
},
util::StrExtension,
};
use strict_num_extended::NonNegativeF64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StopProcessor {
case_sensitive_obj_id: Rc<RefCell<bool>>,
}
impl StopProcessor {
pub fn new(case_sensitive_obj_id: &Rc<RefCell<bool>>) -> Self {
Self {
case_sensitive_obj_id: Rc::clone(case_sensitive_obj_id),
}
}
}
impl TokenProcessor for StopProcessor {
type Output = StopObjects;
fn process<P: Prompter>(
&self,
ctx: &mut ProcessContext<'_, '_, P>,
) -> core::result::Result<Self::Output, ParseErrorWithRange> {
let mut objects = StopObjects::default();
ctx.all_tokens(|token, prompter| match token.content() {
Token::Header { name, args } => Ok(self
.on_header(name.as_ref(), args.as_ref(), prompter, &mut objects)
.err()
.map(|warn| warn.into_wrapper(token))),
Token::Message {
track,
channel,
message,
} => Ok(self
.on_message(
*track,
*channel,
&message.as_ref().into_wrapper(token),
&mut objects,
prompter,
)
.err()
.map(|warn| warn.into_wrapper(token))),
Token::NotACommand(_) => Ok(None),
})?;
Ok(objects)
}
}
impl StopProcessor {
fn on_header(
&self,
name: &str,
args: &str,
prompter: &impl Prompter,
objects: &mut StopObjects,
) -> Result<()> {
if let Some(id) = name.strip_prefix_ignore_case("STOP") {
let string_value: StringValue<NonNegativeF64> = StringValue::new(args);
let stop_obj_id = ObjId::try_from(id, *self.case_sensitive_obj_id.borrow())?;
if let Some(older) = objects.stop_defs.get_mut(&stop_obj_id) {
prompter
.handle_def_duplication(DefDuplication::Stop {
id: stop_obj_id,
older,
newer: &string_value,
})
.apply_def(older, string_value, stop_obj_id)?;
} else {
objects.stop_defs.insert(stop_obj_id, string_value);
}
}
if name.eq_ignore_ascii_case("STP") {
use std::time::Duration;
let args: Vec<_> = args.split_whitespace().collect();
let [measure_pos, ms] = args.as_slice() else {
return Err(ParseWarning::SyntaxError(
"stp requires 2 arguments: measure.position and milliseconds".into(),
));
};
let (measure, pos) = measure_pos.split_once('.').unwrap_or((measure_pos, "000"));
let measure: u16 = measure
.parse()
.map_err(|_| ParseWarning::SyntaxError("expected measure u16".into()))?;
let pos: u16 = pos
.parse()
.map_err(|_| ParseWarning::SyntaxError("expected pos u16".into()))?;
let ms: u64 = ms
.parse()
.map_err(|_| ParseWarning::SyntaxError("expected pos u64".into()))?;
let time = ObjTime::new(measure as u64, pos as u64, 1000).ok_or_else(|| {
ParseWarning::SyntaxError("denominator should be non-zero".into())
})?;
let duration = Duration::from_millis(ms);
let ev = StpEvent { time, duration };
if let Some(older) = objects.stp_events.get_mut(&time) {
use crate::bms::parse::prompt::ChannelDuplication;
prompter
.handle_channel_duplication(ChannelDuplication::StpEvent {
time,
older,
newer: &ev,
})
.apply_channel(older, ev, time, Channel::Stop)?;
} else {
objects.stp_events.insert(time, ev);
}
}
Ok(())
}
fn on_message(
&self,
track: Track,
channel: Channel,
message: &SourceRangeMixin<&str>,
objects: &mut StopObjects,
prompter: &impl Prompter,
) -> core::result::Result<Vec<ParseWarningWithRange>, ParseWarning> {
let mut warnings: Vec<ParseWarningWithRange> = Vec::new();
if channel == Channel::Stop {
let (pairs, w) = parse_obj_ids(track, message, &self.case_sensitive_obj_id);
warnings.extend(w);
for (time, obj) in pairs {
objects.stop_ids_used.insert(obj);
let string_value = objects
.stop_defs
.get(&obj)
.ok_or(ParseWarning::UndefinedObject(obj))?;
let Ok(&duration) = string_value.value().as_ref() else {
continue;
};
objects.push_stop(StopObj { time, duration }, prompter)?;
}
}
Ok(warnings)
}
}