1use core::fmt;
18use core::fmt::{Display, Formatter};
19use std::error::Error;
20
21pub type Point2 = [f64; 2];
22pub type Point3 = [f64; 3];
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct ParseError {
26 error: String,
27}
28
29impl ParseError {
30 fn from_string(error: String) -> Self {
31 Self { error }
32 }
33
34 fn float_parse_error(str: &str, label: &str, error: impl Error) -> Self {
35 ParseError::from_string(format!("Failed to parse {} ({}) as f64: {}", str, label, error))
36 }
37
38 fn unexpected_entries(num_entries_found: usize, num_entries_expected: usize) -> Self {
39 ParseError::from_string(format!(
40 "Found {} entries in line, but expected {}",
41 num_entries_found, num_entries_expected
42 ))
43 }
44}
45
46fn try_parse_f64(str: &str, label: &str) -> Result<f64, ParseError> {
47 str.parse::<f64>()
48 .map_err(|err| ParseError::float_parse_error(str, label, err))
49}
50
51impl Display for ParseError {
52 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
53 write!(f, "{}", self.error)
54 }
55}
56
57impl std::error::Error for ParseError {}
58
59#[derive(Clone, Default, PartialEq)]
60pub struct Rule2d {
61 pub points: Vec<Point2>,
62 pub weights: Vec<f64>,
63}
64
65#[derive(Clone, Default, PartialEq)]
66pub struct Rule3d {
67 pub points: Vec<Point3>,
68 pub weights: Vec<f64>,
69}
70
71fn parse_helper(
72 data: &str,
73 labels: &[&str],
74 mut handler: impl FnMut(&[f64]) -> Result<(), ParseError>,
75) -> Result<(), ParseError> {
76 let mut line_numbers = Vec::new();
77
78 for line in data.lines() {
79 let mut iter = line.split_ascii_whitespace().peekable();
80
81 if iter.peek().is_some() {
83 line_numbers.clear();
84
85 for (i, entry) in iter.enumerate() {
86 let label = labels.get(i).unwrap_or_else(|| &"unlabeled entry");
87 let coord_entry = try_parse_f64(entry, label)?;
88 line_numbers.push(coord_entry);
89 }
90
91 handler(&line_numbers)?;
92 }
93 }
94 Ok(())
95}
96
97pub fn parse2d(data: &str) -> Result<Rule2d, ParseError> {
105 let mut rule = Rule2d::default();
106 parse_helper(
107 data,
108 &["x coordinate", "y coordinate", "weight"],
109 |entries| match entries {
110 [x, y, w] => {
111 rule.points.push([*x, *y]);
112 rule.weights.push(*w);
113 Ok(())
114 }
115 _ => Err(ParseError::unexpected_entries(entries.len(), 3)),
116 },
117 )?;
118 Ok(rule)
119}
120
121pub fn parse3d(data: &str) -> Result<Rule3d, ParseError> {
129 let mut rule = Rule3d::default();
130 let labels = &["x coordinate", "y coordinate", "z coordinate", "weight"];
131 parse_helper(data, labels, |entries| match entries {
132 [x, y, z, w] => {
133 rule.points.push([*x, *y, *z]);
134 rule.weights.push(*w);
135 Ok(())
136 }
137 _ => Err(ParseError::unexpected_entries(entries.len(), 4)),
138 })?;
139 Ok(rule)
140}