sgf_parse/props/
parse.rs

1use std::collections::HashSet;
2use std::hash::Hash;
3use std::str::FromStr;
4
5use super::SgfPropError;
6
7pub trait FromCompressedList: Sized {
8    fn from_compressed_list(
9        upper_left: &Self,
10        lower_right: &Self,
11    ) -> Result<HashSet<Self>, SgfPropError>;
12}
13
14pub fn parse_single_value<T: FromStr>(values: &[String]) -> Result<T, SgfPropError> {
15    if values.len() != 1 {
16        return Err(SgfPropError {});
17    }
18    values[0].parse().map_err(|_| SgfPropError {})
19}
20
21pub fn parse_tuple<T1: FromStr, T2: FromStr>(value: &str) -> Result<(T1, T2), SgfPropError> {
22    let (s1, s2) = split_compose(value)?;
23    Ok((
24        s1.parse().map_err(|_| SgfPropError {})?,
25        s2.parse().map_err(|_| SgfPropError {})?,
26    ))
27}
28
29pub fn parse_elist<T: FromStr + FromCompressedList + Eq + Hash>(
30    values: &[String],
31) -> Result<HashSet<T>, SgfPropError> {
32    let mut elements = HashSet::new();
33    for value in values {
34        if value.contains(':') {
35            let (upper_left, lower_right): (T, T) = parse_tuple(value)?;
36            elements.extend(T::from_compressed_list(&upper_left, &lower_right)?);
37        } else {
38            let item = value.parse().map_err(|_| SgfPropError {})?;
39            elements.insert(item);
40        }
41    }
42    Ok(elements)
43}
44
45pub fn parse_list<T: FromStr + FromCompressedList + Eq + std::hash::Hash>(
46    values: &[String],
47) -> Result<HashSet<T>, SgfPropError> {
48    let points = parse_elist::<T>(values)?;
49    if points.is_empty() {
50        return Err(SgfPropError {});
51    }
52
53    Ok(points)
54}
55
56pub fn parse_list_composed<T: FromStr + Eq + Hash>(
57    values: &[String],
58) -> Result<HashSet<(T, T)>, SgfPropError> {
59    let mut pairs = HashSet::new();
60    for value in values.iter() {
61        let pair = parse_tuple(value)?;
62        if pair.0 == pair.1 || pairs.contains(&pair) {
63            return Err(SgfPropError {});
64        }
65        pairs.insert(pair);
66    }
67
68    Ok(pairs)
69}
70
71pub fn split_compose(value: &str) -> Result<(&str, &str), SgfPropError> {
72    let parts: Vec<&str> = value.split(':').collect();
73    if parts.len() != 2 {
74        return Err(SgfPropError {});
75    }
76
77    Ok((parts[0], parts[1]))
78}
79
80pub fn verify_empty(values: &[String]) -> Result<(), SgfPropError> {
81    if !(values.is_empty() || (values.len() == 1 && values[0].is_empty())) {
82        return Err(SgfPropError {});
83    }
84    Ok(())
85}
86
87#[cfg(test)]
88mod test {
89    use super::parse_list;
90    use crate::go::Point;
91    use std::collections::HashSet;
92
93    #[test]
94    pub fn parse_list_point() {
95        let values = vec!["pq:ss".to_string(), "so".to_string(), "lr:ns".to_string()];
96        let expected: HashSet<_> = vec![
97            (15, 16),
98            (16, 16),
99            (17, 16),
100            (18, 16),
101            (15, 17),
102            (16, 17),
103            (17, 17),
104            (18, 17),
105            (15, 18),
106            (16, 18),
107            (17, 18),
108            (18, 18),
109            (18, 14),
110            (11, 17),
111            (12, 17),
112            (13, 17),
113            (11, 18),
114            (12, 18),
115            (13, 18),
116        ]
117        .into_iter()
118        .map(|(x, y)| Point { x, y })
119        .collect();
120
121        let result: HashSet<_> = parse_list::<Point>(&values).unwrap();
122
123        assert_eq!(result, expected);
124    }
125}