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}