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