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}