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(test)]
142mod tests {
143    use super::{
144        super::{
145            super::tests::{parse_span as p, parse_term as t},
146            RangeCard::{self, *},
147            RankConst as R, Span, SpanElem as E, SuitConst as S,
148        },
149        Error,
150    };
151    use crate::*;
152
153    const fn valid_card(c: RangeCard) -> bool {
154        matches!(c, CC(_, _) | CA(_))
155    }
156
157    fn els(cs: &[RangeCard]) -> Vec<E> {
158        cs.iter()
159            .map(|c| match c {
160                CC(r, s) => E::CC(*r, *s),
161                CA(r) => E::CA(*r),
162                _ => panic!("invalid span element {c:?}"),
163            })
164            .collect()
165    }
166
167    #[quickcheck]
168    fn test_spandown(c1: RangeCard, c2: RangeCard) -> TestResult {
169        if !valid_card(c1) || !valid_card(c2) {
170            return TestResult::discard();
171        }
172
173        let src = format!("{}{}-", c1.to_src(), c2.to_src());
174
175        assert_eq!(p(&src), Ok(Span::Down(els(&[c1, c2]))));
176
177        TestResult::passed()
178    }
179
180    #[quickcheck]
181    fn test_spanup(c1: RangeCard, c2: RangeCard) -> TestResult {
182        if !valid_card(c1) || !valid_card(c2) {
183            return TestResult::discard();
184        }
185
186        let src = format!("{}{}+", c1.to_src(), c2.to_src());
187
188        assert_eq!(p(&src), Ok(Span::Up(els(&[c1, c2]))));
189
190        TestResult::passed()
191    }
192
193    #[quickcheck]
194    fn test_spanto(a1: R, a2: R, b1: R, b2: R, s: S) -> TestResult {
195        if (a1 as i8 - a2 as i8) != (b1 as i8 - b2 as i8) {
196            return TestResult::discard();
197        }
198
199        let src = format!(
200            "{}{}{}-{}{}{}",
201            a1.to_src(),
202            b1.to_src(),
203            s.to_src(),
204            a2.to_src(),
205            b2.to_src(),
206            s.to_src(),
207        );
208
209        let v1 = els(&[RangeCard::CA(a1), RangeCard::CC(b1, s)]);
210        let v2 = els(&[RangeCard::CA(a2), RangeCard::CC(b2, s)]);
211
212        let (top, btm) = if a1 > a2 { (v1, v2) } else { (v2, v1) };
213
214        assert_eq!(p(&src), Ok(Span::To(top, btm)));
215
216        TestResult::passed()
217    }
218
219    #[test]
220    fn test_spanto_fixed() {
221        const C_AS: E = E::CC(R::RA, S::S);
222        const C_K: E = E::CA(R::RK);
223        const C_JS: E = E::CC(R::RJ, S::S);
224        const C_T: E = E::CA(R::RT);
225
226        let span = p("AsK-JsT").unwrap();
227
228        assert_eq!(span, Span::To(vec![C_AS, C_K], vec![C_JS, C_T]));
229        assert_eq!(p("T-A"), p("A-T"));
230        assert_eq!(p("TT-AA"), p("AA-TT"));
231    }
232
233    #[test]
234    fn test_span_error() {
235        assert_eq!(
236            p("A-AK"),
237            Err(Error::NumberOfRanksMismatchInSpan((0, 4)).into())
238        );
239
240        assert_eq!(
241            p("AA-QT"),
242            Err(Error::RankDistanceMismatchInSpan((0, 5)).into())
243        );
244
245        assert_eq!(p("As-K"), Err(Error::SuitMismatchInSpan((0, 4)).into()));
246    }
247
248    #[test]
249    fn test_span_error_invalid() {
250        assert_eq!(p("B-    "), Err(Error::InvalidSpan((0, 2)).into()));
251        assert_eq!(p("Bs-   "), Err(Error::InvalidSpan((0, 3)).into()));
252        assert_eq!(p("*w-   "), Err(Error::InvalidSpan((0, 3)).into()));
253        assert_eq!(p("Aw-   "), Err(Error::InvalidSpan((0, 3)).into()));
254        assert_eq!(p("Bw-   "), Err(Error::InvalidSpan((0, 3)).into()));
255        assert_eq!(p("*-    "), Err(Error::InvalidSpan((0, 2)).into()));
256        assert_eq!(t("A[A]+ "), Err(Error::InvalidSpan((0, 5)).into()));
257        assert_eq!(t("A[A-]-"), Err(Error::InvalidSpan((0, 6)).into()));
258        assert_eq!(t("[A]-A "), Err(Error::InvalidSpan((0, 5)).into()));
259        assert_eq!(t("A-[A] "), Err(Error::InvalidSpan((0, 5)).into()));
260    }
261}