skip parsed line if expected field not present
This commit is contained in:
@@ -19,8 +19,6 @@ pub enum ParseError {
|
|||||||
InvalidDecimalNumber,
|
InvalidDecimalNumber,
|
||||||
/// Failed to parse game mode.
|
/// Failed to parse game mode.
|
||||||
InvalidMode,
|
InvalidMode,
|
||||||
/// Expected an additional field.
|
|
||||||
MissingField(&'static str),
|
|
||||||
/// Failed to recognized specified type for hitobjects.
|
/// Failed to recognized specified type for hitobjects.
|
||||||
UnknownHitObjectKind,
|
UnknownHitObjectKind,
|
||||||
}
|
}
|
||||||
@@ -36,7 +34,6 @@ impl fmt::Display for ParseError {
|
|||||||
Self::InvalidCurvePoints => f.write_str("invalid curve point"),
|
Self::InvalidCurvePoints => f.write_str("invalid curve point"),
|
||||||
Self::InvalidDecimalNumber => f.write_str("invalid float number"),
|
Self::InvalidDecimalNumber => f.write_str("invalid float number"),
|
||||||
Self::InvalidMode => f.write_str("invalid mode"),
|
Self::InvalidMode => f.write_str("invalid mode"),
|
||||||
Self::MissingField(field) => write!(f, "missing field `{field}`"),
|
|
||||||
Self::UnknownHitObjectKind => f.write_str("unsupported hitobject kind"),
|
Self::UnknownHitObjectKind => f.write_str("unsupported hitobject kind"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +48,6 @@ impl StdError for ParseError {
|
|||||||
Self::InvalidCurvePoints => None,
|
Self::InvalidCurvePoints => None,
|
||||||
Self::InvalidDecimalNumber => None,
|
Self::InvalidDecimalNumber => None,
|
||||||
Self::InvalidMode => None,
|
Self::InvalidMode => None,
|
||||||
Self::MissingField(_) => None,
|
|
||||||
Self::UnknownHitObjectKind => None,
|
Self::UnknownHitObjectKind => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-97
@@ -33,16 +33,6 @@ use crate::{
|
|||||||
util::{SortedVec, TandemSorter},
|
util::{SortedVec, TandemSorter},
|
||||||
};
|
};
|
||||||
|
|
||||||
trait OptionExt<T> {
|
|
||||||
fn next_field(self, field: &'static str) -> Result<T, ParseError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> OptionExt<T> for Option<T> {
|
|
||||||
fn next_field(self, field: &'static str) -> Result<T, ParseError> {
|
|
||||||
self.ok_or(ParseError::MissingField(field))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait InRange: Sized + Copy + Neg<Output = Self> + PartialOrd + FromStr {
|
trait InRange: Sized + Copy + Neg<Output = Self> + PartialOrd + FromStr {
|
||||||
const LIMIT: Self;
|
const LIMIT: Self;
|
||||||
|
|
||||||
@@ -239,15 +229,8 @@ macro_rules! parse_events_body {
|
|||||||
|
|
||||||
// We're only interested in breaks
|
// We're only interested in breaks
|
||||||
if let Some(b'2') = split.next().and_then(|value| value.bytes().next()) {
|
if let Some(b'2') = split.next().and_then(|value| value.bytes().next()) {
|
||||||
let start_time = split
|
let start_time = split.next().and_then(f64::parse_in_range);
|
||||||
.next()
|
let end_time = split.next().and_then(f64::parse_in_range);
|
||||||
.next_field("break start")
|
|
||||||
.map(f64::parse_in_range)?;
|
|
||||||
|
|
||||||
let end_time = split
|
|
||||||
.next()
|
|
||||||
.next_field("break end")
|
|
||||||
.map(f64::parse_in_range)?;
|
|
||||||
|
|
||||||
if let (Some(start_time), Some(end_time)) = (start_time, end_time) {
|
if let (Some(start_time), Some(end_time)) = (start_time, end_time) {
|
||||||
$self.breaks.push(Break {
|
$self.breaks.push(Break {
|
||||||
@@ -279,13 +262,7 @@ macro_rules! parse_timingpoints_body {
|
|||||||
let line = $reader.get_line()?;
|
let line = $reader.get_line()?;
|
||||||
let mut split = line.split(',');
|
let mut split = line.split(',');
|
||||||
|
|
||||||
let time_opt = split
|
let time = match split.next().map(str::trim).and_then(f64::parse_in_range) {
|
||||||
.next()
|
|
||||||
.next_field("timing point time")
|
|
||||||
.map(str::trim)
|
|
||||||
.map(f64::parse_in_range)?;
|
|
||||||
|
|
||||||
let time = match time_opt {
|
|
||||||
Some(time) => time,
|
Some(time) => time,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
@@ -293,11 +270,10 @@ macro_rules! parse_timingpoints_body {
|
|||||||
// * beatLength is allowed to be NaN to handle an edge case in which
|
// * beatLength is allowed to be NaN to handle an edge case in which
|
||||||
// * some beatmaps use NaN slider velocity to disable slider tick
|
// * some beatmaps use NaN slider velocity to disable slider tick
|
||||||
// * generation (see LegacyDifficultyControlPoint).
|
// * generation (see LegacyDifficultyControlPoint).
|
||||||
let beat_len: f64 = split.next().next_field("beat len")?.trim().parse()?;
|
let beat_len = match split.next().map(str::trim).map(str::parse::<f64>) {
|
||||||
|
Some(Ok(beat_len)) if beat_len.is_in_range() || beat_len.is_nan() => beat_len,
|
||||||
if !(beat_len.is_in_range() || beat_len.is_nan()) {
|
_ => continue,
|
||||||
continue;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
let mut timing_change = true;
|
let mut timing_change = true;
|
||||||
let mut kiai = false;
|
let mut kiai = false;
|
||||||
@@ -428,14 +404,12 @@ macro_rules! parse_hitobjects_body {
|
|||||||
|
|
||||||
let x = split
|
let x = split
|
||||||
.next()
|
.next()
|
||||||
.next_field("x pos")
|
.and_then(|s| f32::parse_in_custom_range(s, MAX_COORDINATE_VALUE as f32))
|
||||||
.map(|s| f32::parse_in_custom_range(s, MAX_COORDINATE_VALUE as f32))?
|
|
||||||
.map(|x| x as i32 as f32);
|
.map(|x| x as i32 as f32);
|
||||||
|
|
||||||
let y = split
|
let y = split
|
||||||
.next()
|
.next()
|
||||||
.next_field("y pos")
|
.and_then(|s| f32::parse_in_custom_range(s, MAX_COORDINATE_VALUE as f32))
|
||||||
.map(|s| f32::parse_in_custom_range(s, MAX_COORDINATE_VALUE as f32))?
|
|
||||||
.map(|x| x as i32 as f32);
|
.map(|x| x as i32 as f32);
|
||||||
|
|
||||||
let pos = if let (Some(x), Some(y)) = (x, y) {
|
let pos = if let (Some(x), Some(y)) = (x, y) {
|
||||||
@@ -444,13 +418,7 @@ macro_rules! parse_hitobjects_body {
|
|||||||
continue 'next_line;
|
continue 'next_line;
|
||||||
};
|
};
|
||||||
|
|
||||||
let time_opt = split
|
let time = match split.next().map(str::trim).and_then(f64::parse_in_range) {
|
||||||
.next()
|
|
||||||
.next_field("hitobject time")
|
|
||||||
.map(str::trim)
|
|
||||||
.map(f64::parse_in_range)?;
|
|
||||||
|
|
||||||
let time = match time_opt {
|
|
||||||
Some(time) => time,
|
Some(time) => time,
|
||||||
None => continue 'next_line,
|
None => continue 'next_line,
|
||||||
};
|
};
|
||||||
@@ -459,64 +427,47 @@ macro_rules! parse_hitobjects_body {
|
|||||||
unsorted = true;
|
unsorted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let kind: u8 = match split.next().next_field("hitobject kind")?.parse() {
|
let kind = match split.next().map(str::parse::<u8>) {
|
||||||
Ok(kind) => kind,
|
Some(Ok(kind)) => kind,
|
||||||
Err(_) => continue 'next_line,
|
_ => continue 'next_line,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut sound: u8 = match split.next().next_field("sound")?.parse() {
|
let mut sound = match split.next().map(str::parse::<u8>) {
|
||||||
Ok(sound) => sound,
|
Some(Ok(sound)) => sound,
|
||||||
Err(_) => continue 'next_line,
|
_ => continue 'next_line,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
fn has_custom_sound_file(bank_info: Option<&str>) -> Option<bool> {
|
||||||
enum Status {
|
|
||||||
Ok(bool),
|
|
||||||
Skip,
|
|
||||||
Err(ParseError),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_custom_sound_file(bank_info: Option<&str>) -> Status {
|
|
||||||
let mut split = match bank_info {
|
let mut split = match bank_info {
|
||||||
Some(s) if !s.is_empty() => s.split(':'),
|
Some(s) if !s.is_empty() => s.split(':'),
|
||||||
_ => return Status::Ok(false),
|
_ => return Some(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
split.next().and_then(i32::parse_in_range)?;
|
||||||
|
split.next().and_then(i32::parse_in_range)?;
|
||||||
|
|
||||||
match split.next().map(i32::parse_in_range) {
|
match split.next().map(i32::parse_in_range) {
|
||||||
Some(Some(_)) => {}
|
Some(Some(_)) => {}
|
||||||
Some(None) => return Status::Skip,
|
None => return Some(false),
|
||||||
None => return Status::Err(ParseError::MissingField("normal set")),
|
Some(None) => return None,
|
||||||
}
|
}
|
||||||
|
|
||||||
match split.next().map(i32::parse_in_range) {
|
match split.next().map(i32::parse_in_range) {
|
||||||
Some(Some(_)) => {}
|
Some(Some(_)) => {}
|
||||||
Some(None) => return Status::Skip,
|
None => return Some(false),
|
||||||
None => return Status::Err(ParseError::MissingField("additional set")),
|
Some(None) => return None,
|
||||||
}
|
|
||||||
|
|
||||||
match split.next().map(i32::parse_in_range) {
|
|
||||||
Some(Some(_)) => {}
|
|
||||||
None => return Status::Ok(false),
|
|
||||||
Some(None) => return Status::Skip,
|
|
||||||
}
|
|
||||||
|
|
||||||
match split.next().map(i32::parse_in_range) {
|
|
||||||
Some(Some(_)) => {}
|
|
||||||
None => return Status::Ok(false),
|
|
||||||
Some(None) => return Status::Skip,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let filename = split.next().filter(|filename| !filename.is_empty());
|
let filename = split.next().filter(|filename| !filename.is_empty());
|
||||||
|
|
||||||
Status::Ok(filename.is_some())
|
Some(filename.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
let kind = if kind & Self::CIRCLE_FLAG > 0 {
|
let kind = if kind & Self::CIRCLE_FLAG > 0 {
|
||||||
match has_custom_sound_file(split.next()) {
|
match has_custom_sound_file(split.next()) {
|
||||||
Status::Ok(false) => {}
|
Some(false) => {}
|
||||||
Status::Ok(true) => sound = 0,
|
Some(true) => sound = 0,
|
||||||
Status::Skip => continue 'next_line,
|
None => continue 'next_line,
|
||||||
Status::Err(err) => return Err(err),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$self.n_circles += 1;
|
$self.n_circles += 1;
|
||||||
@@ -528,13 +479,16 @@ macro_rules! parse_hitobjects_body {
|
|||||||
// Control Points: [1, 94872] | Median=3 | Mean=2.9984
|
// Control Points: [1, 94872] | Median=3 | Mean=2.9984
|
||||||
let mut control_points = Vec::with_capacity(3);
|
let mut control_points = Vec::with_capacity(3);
|
||||||
|
|
||||||
let control_point_iter = split.next().next_field("control points")?.split('|');
|
let control_point_iter = match split.next() {
|
||||||
|
Some(s) => s.split('|'),
|
||||||
|
None => continue 'next_line,
|
||||||
|
};
|
||||||
|
|
||||||
let repeats = match split.next().next_field("repeats")?.parse::<usize>() {
|
let repeats = match split.next().map(str::parse::<usize>) {
|
||||||
// * osu-stable treated the first span of the slider
|
// * osu-stable treated the first span of the slider
|
||||||
// * as a repeat, but no repeats are happening
|
// * as a repeat, but no repeats are happening
|
||||||
Ok(repeats @ 0..=9000) => repeats.saturating_sub(1),
|
Some(Ok(repeats @ 0..=9000)) => repeats.saturating_sub(1),
|
||||||
Ok(_) | Err(_) => continue 'next_line,
|
_ => continue 'next_line,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut start_idx = 0;
|
let mut start_idx = 0;
|
||||||
@@ -622,10 +576,9 @@ macro_rules! parse_hitobjects_body {
|
|||||||
// Note: Edge sets are currently not considered, seems to be fine though.
|
// Note: Edge sets are currently not considered, seems to be fine though.
|
||||||
|
|
||||||
match has_custom_sound_file(split.nth(1)) {
|
match has_custom_sound_file(split.nth(1)) {
|
||||||
Status::Ok(false) => {}
|
Some(false) => {}
|
||||||
Status::Ok(true) => sound = 0,
|
Some(true) => sound = 0,
|
||||||
Status::Skip => continue 'next_line,
|
None => continue 'next_line,
|
||||||
Status::Err(err) => return Err(err),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HitObjectKind::Slider {
|
HitObjectKind::Slider {
|
||||||
@@ -638,16 +591,15 @@ macro_rules! parse_hitobjects_body {
|
|||||||
} else if kind & Self::SPINNER_FLAG > 0 {
|
} else if kind & Self::SPINNER_FLAG > 0 {
|
||||||
$self.n_spinners += 1;
|
$self.n_spinners += 1;
|
||||||
|
|
||||||
let end_time = match split.next().next_field("spinner endtime")?.parse::<f64>() {
|
let end_time = match split.next().map(str::parse::<f64>) {
|
||||||
Ok(end_time) => end_time.max(time),
|
Some(Ok(end_time)) => end_time.max(time),
|
||||||
Err(_) => continue 'next_line,
|
_ => continue 'next_line,
|
||||||
};
|
};
|
||||||
|
|
||||||
match has_custom_sound_file(split.next()) {
|
match has_custom_sound_file(split.next()) {
|
||||||
Status::Ok(false) => {}
|
Some(false) => {}
|
||||||
Status::Ok(true) => sound = 0,
|
Some(true) => sound = 0,
|
||||||
Status::Skip => continue 'next_line,
|
None => continue 'next_line,
|
||||||
Status::Err(err) => return Err(err),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HitObjectKind::Spinner { end_time }
|
HitObjectKind::Spinner { end_time }
|
||||||
@@ -662,10 +614,9 @@ macro_rules! parse_hitobjects_body {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match has_custom_sound_file(Some(tail)) {
|
match has_custom_sound_file(Some(tail)) {
|
||||||
Status::Ok(false) => {}
|
Some(false) => {}
|
||||||
Status::Ok(true) => sound = 0,
|
Some(true) => sound = 0,
|
||||||
Status::Skip => continue 'next_line,
|
None => continue 'next_line,
|
||||||
Status::Err(err) => return Err(err),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed
|
parsed
|
||||||
|
|||||||
Reference in New Issue
Block a user