use core::fmt;
use core::fmt::{Display, Formatter};
use std::error::Error;
pub type Point2 = [f64; 2];
pub type Point3 = [f64; 3];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseError {
error: String,
}
impl ParseError {
fn from_string(error: String) -> Self {
Self { error }
}
fn float_parse_error(str: &str, label: &str, error: impl Error) -> Self {
ParseError::from_string(format!("Failed to parse {} ({}) as f64: {}", str, label, error))
}
fn unexpected_entries(num_entries_found: usize, num_entries_expected: usize) -> Self {
ParseError::from_string(format!(
"Found {} entries in line, but expected {}",
num_entries_found, num_entries_expected
))
}
}
fn try_parse_f64(str: &str, label: &str) -> Result<f64, ParseError> {
str.parse::<f64>()
.map_err(|err| ParseError::float_parse_error(str, label, err))
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error)
}
}
impl std::error::Error for ParseError {}
#[derive(Clone, Default, PartialEq)]
pub struct Rule2d {
pub points: Vec<Point2>,
pub weights: Vec<f64>,
}
#[derive(Clone, Default, PartialEq)]
pub struct Rule3d {
pub points: Vec<Point3>,
pub weights: Vec<f64>,
}
fn parse_helper(
data: &str,
labels: &[&str],
mut handler: impl FnMut(&[f64]) -> Result<(), ParseError>,
) -> Result<(), ParseError> {
let mut line_numbers = Vec::new();
for line in data.lines() {
let mut iter = line.split_ascii_whitespace().peekable();
if iter.peek().is_some() {
line_numbers.clear();
for (i, entry) in iter.enumerate() {
let label = labels.get(i).unwrap_or_else(|| &"unlabeled entry");
let coord_entry = try_parse_f64(entry, label)?;
line_numbers.push(coord_entry);
}
handler(&line_numbers)?;
}
}
Ok(())
}
pub fn parse2d(data: &str) -> Result<Rule2d, ParseError> {
let mut rule = Rule2d::default();
parse_helper(
data,
&["x coordinate", "y coordinate", "weight"],
|entries| match entries {
[x, y, w] => {
rule.points.push([*x, *y]);
rule.weights.push(*w);
Ok(())
}
_ => Err(ParseError::unexpected_entries(entries.len(), 3)),
},
)?;
Ok(rule)
}
pub fn parse3d(data: &str) -> Result<Rule3d, ParseError> {
let mut rule = Rule3d::default();
let labels = &["x coordinate", "y coordinate", "z coordinate", "weight"];
parse_helper(data, labels, |entries| match entries {
[x, y, z, w] => {
rule.points.push([*x, *y, *z]);
rule.weights.push(*w);
Ok(())
}
_ => Err(ParseError::unexpected_entries(entries.len(), 4)),
})?;
Ok(rule)
}