open_pql/range_parser/ast/
span.rs

1use super::{
2    Error, LalrError, Loc, RangeCard, RankConst as RankC, ResultE,
3    SuitConst as SuitC, TermElem,
4};
5
6#[derive(Copy, Clone, PartialEq, Eq, Debug)]
7pub enum Elem {
8    CC(RankC, SuitC),
9    CA(RankC),
10}
11
12impl Elem {
13    pub const fn rank(self) -> RankC {
14        match self {
15            Self::CC(r, _) | Self::CA(r) => r,
16        }
17    }
18
19    pub const fn suit(self) -> Option<SuitC> {
20        match self {
21            Self::CC(_, s) => Some(s),
22            Self::CA(_) => None,
23        }
24    }
25}
26
27impl TryFrom<(Loc, Loc, RangeCard)> for Elem {
28    type Error = LalrError<'static>;
29
30    fn try_from((l, r, c): (Loc, Loc, RangeCard)) -> Result<Self, Self::Error> {
31        match c {
32            RangeCard::CC(r, s) => Ok(Self::CC(r, s)),
33            RangeCard::CA(r) => Ok(Self::CA(r)),
34            _ => Err(Error::InvalidSpan((l, r)).into()),
35        }
36    }
37}
38
39impl TryFrom<(Loc, Loc, TermElem)> for Elem {
40    type Error = LalrError<'static>;
41
42    fn try_from((l, r, e): (Loc, Loc, TermElem)) -> Result<Self, Self::Error> {
43        match e {
44            TermElem::Card(c) => Self::try_from((l, r, c)),
45            _ => Err(Error::InvalidSpan((l, r)).into()),
46        }
47    }
48}
49
50#[derive(Clone, PartialEq, Eq, Debug)]
51pub enum Span {
52    Down(Vec<Elem>),
53    Up(Vec<Elem>),
54    To(Vec<Elem>, Vec<Elem>),
55}
56
57#[inline]
58fn to_span_elems<'i, T>(l: Loc, r: Loc, cs: Vec<T>) -> ResultE<'i, Vec<Elem>>
59where
60    Elem: TryFrom<(Loc, Loc, T), Error = LalrError<'i>>,
61{
62    cs.into_iter().map(|c| Elem::try_from((l, r, c))).collect()
63}
64
65#[inline]
66fn ensure_same_format<'i>(
67    l: Loc,
68    r: Loc,
69    v1: &[Elem],
70    v2: &[Elem],
71) -> ResultE<'i, ()> {
72    #[inline]
73    const fn is_same_distance(
74        v1: &[Elem],
75        v2: &[Elem],
76        i: usize,
77        j: usize,
78    ) -> bool {
79        v1[j].rank() as i8 - v1[i].rank() as i8
80            == v2[j].rank() as i8 - v2[i].rank() as i8
81    }
82
83    let len = v1.len();
84
85    if v2.len() != len {
86        return Err(Error::NumberOfRanksMismatchInSpan((l, r)).into());
87    }
88
89    for i in 0..len {
90        if i < len - 1 && !is_same_distance(v1, v2, i, i + 1) {
91            return Err(Error::RankDistanceMismatchInSpan((l, r)).into());
92        }
93        if v1[i].suit() != v2[i].suit() {
94            return Err(Error::SuitMismatchInSpan((l, r)).into());
95        }
96    }
97
98    Ok(())
99}
100
101impl<'i> Span {
102    #[allow(clippy::needless_pass_by_value)]
103    pub(crate) fn spandown<T>(l: Loc, cs: Vec<T>, r: Loc) -> ResultE<'i, Self>
104    where
105        Elem: TryFrom<(Loc, Loc, T), Error = LalrError<'i>>,
106    {
107        Ok(Self::Down(to_span_elems(l, r, cs)?))
108    }
109
110    #[allow(clippy::needless_pass_by_value)]
111    pub(crate) fn spanup<T>(l: Loc, cs: Vec<T>, r: Loc) -> ResultE<'i, Self>
112    where
113        Elem: TryFrom<(Loc, Loc, T), Error = LalrError<'i>>,
114    {
115        Ok(Self::Up(to_span_elems(l, r, cs)?))
116    }
117
118    #[allow(clippy::needless_pass_by_value)]
119    pub(crate) fn spanto<T>(
120        l: Loc,
121        top: Vec<T>,
122        btm: Vec<T>,
123        r: Loc,
124    ) -> ResultE<'i, Self>
125    where
126        Elem: TryFrom<(Loc, Loc, T), Error = LalrError<'i>>,
127    {
128        let t = to_span_elems(l, r, top)?;
129        let b = to_span_elems(l, r, btm)?;
130
131        ensure_same_format(l, r, &t, &b).map(|()| {
132            if t[0].rank() > b[0].rank() {
133                Self::To(t, b)
134            } else {
135                Self::To(b, t)
136            }
137        })
138    }
139}
140
141#[cfg_attr(coverage_nightly, coverage(off))]
142#[cfg(test)]
143mod tests {
144    use super::{
145        super::{
146            super::tests::{parse_span as p, parse_term as t},
147            RangeCard::{self, *},
148            RankConst as R, Span, SpanElem as E, SuitConst as S,
149        },
150        Error,
151    };
152    use crate::*;
153
154    const fn valid_card(c: RangeCard) -> bool {
155        matches!(c, CC(_, _) | CA(_))
156    }
157
158    fn els(cs: &[RangeCard]) -> Vec<E> {
159        cs.iter()
160            .map(|c| match c {
161                CC(r, s) => E::CC(*r, *s),
162                CA(r) => E::CA(*r),
163                _ => panic!("invalid span element {c:?}"),
164            })
165            .collect()
166    }
167
168    #[quickcheck]
169    fn test_spandown(c1: RangeCard, c2: RangeCard) -> TestResult {
170        if !valid_card(c1) || !valid_card(c2) {
171            return TestResult::discard();
172        }
173
174        let src = format!("{}{}-", c1.to_src(), c2.to_src());
175
176        assert_eq!(p(&src), Ok(Span::Down(els(&[c1, c2]))));
177
178        TestResult::passed()
179    }
180
181    #[quickcheck]
182    fn test_spanup(c1: RangeCard, c2: RangeCard) -> TestResult {
183        if !valid_card(c1) || !valid_card(c2) {
184            return TestResult::discard();
185        }
186
187        let src = format!("{}{}+", c1.to_src(), c2.to_src());
188
189        assert_eq!(p(&src), Ok(Span::Up(els(&[c1, c2]))));
190
191        TestResult::passed()
192    }
193
194    #[quickcheck]
195    fn test_spanto(a1: R, a2: R, b1: R, b2: R, s: S) -> TestResult {
196        if (a1 as i8 - a2 as i8) != (b1 as i8 - b2 as i8) {
197            return TestResult::discard();
198        }
199
200        let src = format!(
201            "{}{}{}-{}{}{}",
202            a1.to_src(),
203            b1.to_src(),
204            s.to_src(),
205            a2.to_src(),
206            b2.to_src(),
207            s.to_src(),
208        );
209
210        let v1 = els(&[RangeCard::CA(a1), RangeCard::CC(b1, s)]);
211        let v2 = els(&[RangeCard::CA(a2), RangeCard::CC(b2, s)]);
212
213        let (top, btm) = if a1 > a2 { (v1, v2) } else { (v2, v1) };
214
215        assert_eq!(p(&src), Ok(Span::To(top, btm)));
216
217        TestResult::passed()
218    }
219
220    #[test]
221    fn test_spanto_fixed() {
222        const C_AS: E = E::CC(R::RA, S::S);
223        const C_K: E = E::CA(R::RK);
224        const C_JS: E = E::CC(R::RJ, S::S);
225        const C_T: E = E::CA(R::RT);
226
227        let span = p("AsK-JsT").unwrap();
228
229        assert_eq!(span, Span::To(vec![C_AS, C_K], vec![C_JS, C_T]));
230        assert_eq!(p("T-A"), p("A-T"));
231        assert_eq!(p("TT-AA"), p("AA-TT"));
232    }
233
234    #[test]
235    fn test_span_error() {
236        assert_eq!(
237            p("A-AK"),
238            Err(Error::NumberOfRanksMismatchInSpan((0, 4)).into())
239        );
240
241        assert_eq!(
242            p("AA-QT"),
243            Err(Error::RankDistanceMismatchInSpan((0, 5)).into())
244        );
245
246        assert_eq!(p("As-K"), Err(Error::SuitMismatchInSpan((0, 4)).into()));
247    }
248
249    #[test]
250    fn test_span_error_invalid() {
251        assert_eq!(p("B-    "), Err(Error::InvalidSpan((0, 2)).into()));
252        assert_eq!(p("Bs-   "), Err(Error::InvalidSpan((0, 3)).into()));
253        assert_eq!(p("*w-   "), Err(Error::InvalidSpan((0, 3)).into()));
254        assert_eq!(p("Aw-   "), Err(Error::InvalidSpan((0, 3)).into()));
255        assert_eq!(p("Bw-   "), Err(Error::InvalidSpan((0, 3)).into()));
256        assert_eq!(p("*-    "), Err(Error::InvalidSpan((0, 2)).into()));
257        assert_eq!(t("A[A]+ "), Err(Error::InvalidSpan((0, 5)).into()));
258        assert_eq!(t("A[A-]-"), Err(Error::InvalidSpan((0, 6)).into()));
259        assert_eq!(t("[A]-A "), Err(Error::InvalidSpan((0, 5)).into()));
260        assert_eq!(t("A-[A] "), Err(Error::InvalidSpan((0, 5)).into()));
261    }
262}