Skip to main content

chess/
finite.rs

1use core::marker::PhantomData;
2use std::collections::BTreeMap;
3
4#[cfg(feature = "serde")]
5use serde::Serialize;
6
7/// Finite set, containing all its values.
8pub trait FiniteSet<const N: usize>: Copy + Sized {
9    const ALL: [Self; N];
10    const LEN: usize = N;
11
12    #[inline]
13    fn iter() -> impl Iterator<Item = Self> {
14        Self::ALL.into_iter()
15    }
16
17    #[inline]
18    fn iter_rev() -> impl Iterator<Item = Self> {
19        Self::ALL.into_iter().rev()
20    }
21}
22
23pub trait Empty {
24    const EMPTY: Self;
25
26    fn is_empty(&self) -> bool;
27}
28
29impl Empty for bool {
30    const EMPTY: Self = false;
31
32    fn is_empty(&self) -> bool {
33        !self
34    }
35}
36
37impl Empty for u8 {
38    const EMPTY: Self = 0;
39
40    fn is_empty(&self) -> bool {
41        *self == Self::EMPTY
42    }
43}
44
45impl Empty for u128 {
46    const EMPTY: Self = 0;
47
48    fn is_empty(&self) -> bool {
49        *self == Self::EMPTY
50    }
51}
52
53impl<T: Empty, const N: usize> Empty for [T; N] {
54    const EMPTY: Self = [const { T::EMPTY }; N];
55
56    fn is_empty(&self) -> bool {
57        self.iter().all(Empty::is_empty)
58    }
59}
60
61impl<T> Empty for Option<T> {
62    const EMPTY: Self = None;
63
64    fn is_empty(&self) -> bool {
65        self.is_none()
66    }
67}
68
69impl<T> Empty for Vec<T> {
70    const EMPTY: Self = Vec::new();
71
72    fn is_empty(&self) -> bool {
73        self.is_empty()
74    }
75}
76
77/// "Full" table, containing an element of `T` for every value of `X`.
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub struct Table<X, T, const N: usize> {
80    pub all: [T; N],
81    __: PhantomData<X>,
82}
83
84impl<X, T, const N: usize> Table<X, T, N> {
85    #[inline]
86    pub const fn new(all: [T; N]) -> Self {
87        Self { all, __: PhantomData }
88    }
89}
90
91impl<X, T: Copy, const N: usize> Table<X, T, N> {
92    #[inline]
93    pub const fn filled(value: T) -> Self {
94        Self { all: [value; N], __: PhantomData }
95    }
96}
97
98impl<X, T: Empty, const N: usize> Empty for Table<X, T, N> {
99    const EMPTY: Self = Self::new([const { T::EMPTY }; N]);
100
101    fn is_empty(&self) -> bool {
102        self.all.iter().all(Empty::is_empty)
103    }
104}
105
106impl<X, T: Empty, const N: usize> Table<X, T, N> {
107    #[inline]
108    pub const fn empty() -> Self {
109        Self::EMPTY
110    }
111}
112
113impl<X, T: Default, const N: usize> Default for Table<X, T, N>
114where
115    [T; N]: Default,
116{
117    fn default() -> Self {
118        Self { all: Default::default(), __: PhantomData }
119    }
120}
121
122impl<X, T, const N: usize> From<Table<X, T, N>> for BTreeMap<X, T>
123where
124    T: Copy,
125    X: FiniteSet<N> + Ord,
126{
127    fn from(table: Table<X, T, N>) -> Self {
128        X::iter().zip(table.all).collect()
129    }
130}
131
132#[cfg(feature = "serde")]
133impl<X, T: Serialize, const N: usize> Serialize for Table<X, T, N>
134where
135    T: Copy + Serialize,
136    X: FiniteSet<N> + Ord + Serialize,
137{
138    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
139    where
140        S: serde::Serializer,
141    {
142        Serialize::serialize(&BTreeMap::from(*self), serializer)
143    }
144}
145
146/// Defines a small finite enum, its table type, and optionally its record type.
147///
148/// This macro keeps enum-indexed tables usable in const contexts. It works
149/// around current Rust const-fn limitations by generating inherent const
150/// helpers instead of relying only on trait methods and indexing operators.
151///
152/// Every variant must have an `as label`. The label becomes the canonical
153/// string returned by `name()` and written by `Display`.
154///
155/// Table-only form:
156///
157/// ```ignore
158/// finite_set!(
159///     /// Board square.
160///     Square,
161///     /// Array-backed table keyed by square.
162///     SquareTable {
163///         /// Lower-left square from White's perspective.
164///         A1 = 0 as a1,
165///         B1 = 1 as b1,
166///     }
167/// );
168/// ```
169///
170/// Table-only form with cursor:
171///
172/// ```ignore
173/// finite_set!(
174///     File,
175///     FileTable {
176///         A = 0 as a,
177///         B = 1 as b,
178///     },
179///     FileCursor
180/// );
181/// ```
182///
183/// Attributes are optional at every documented position. Values are optional
184/// when the previous variant's discriminant plus one is the desired value:
185///
186/// ```ignore
187/// finite_set!(Square, SquareTable {
188///     A1 = 0 as a1,
189///     B1 as b1,
190/// });
191/// ```
192///
193/// Record form:
194///
195/// ```ignore
196/// finite_set!(
197///     /// Player to move / piece owner.
198///     Player,
199///     /// Named-field values keyed by player.
200///     Players,
201///     /// Array-backed table keyed by player.
202///     PlayerTable,
203///     {
204///         /// Black player.
205///         Black = 0 as black,
206///         /// White player.
207///         White = 1 as white,
208///     }
209/// );
210/// ```
211///
212/// The generated enum gets:
213/// - `ALL` and `LEN`
214/// - `name`, `eq`, `index`, `from_index`, and `panicky_from_index`
215/// - `iter` and `iter_rev`
216/// - `Display` via `name`
217///
218/// The generated table type is an alias for [`Table`] and gets key-based
219/// indexing plus `get`, `get_ref`, and `get_mut`.
220///
221/// The record form also generates a named-field struct with the same key-based
222/// indexing and accessors.
223///
224/// The public arms calculate the enum length with `@count`, then dispatch to
225/// private arms. `@table` emits the enum, `FiniteSet`, and the array-backed
226/// table alias. `@record` first delegates to `@table`, then adds the named-field
227/// record and its indexing helpers.
228#[macro_export]
229macro_rules! finite_set {
230    (
231        $(#[$name_meta:meta])*
232        $name:ident,
233        $(#[$table_meta:meta])*
234        $table:ident {
235            $($(#[$variant_meta:meta])* $variant:ident $(= $value:tt)? as $field:tt),+ $(,)?
236        }
237    ) => {
238        $crate::finite_set! {
239            @table
240            { $crate::finite_set!(@count $($variant),+) },
241            { $(#[$name_meta])* },
242            $name,
243            { $(#[$table_meta])* },
244            $table,
245            $table {
246                $($(#[$variant_meta])* $variant $(= $value)? as $field),+
247            }
248        }
249    };
250
251    (
252        $(#[$name_meta:meta])*
253        $name:ident,
254        $(#[$table_meta:meta])*
255        $table:ident {
256            $($(#[$variant_meta:meta])* $variant:ident $(= $value:tt)? as $field:tt),+ $(,)?
257        },
258        $cursor:ident
259    ) => {
260        $crate::finite_set! {
261            @table
262            { $crate::finite_set!(@count $($variant),+) },
263            { $(#[$name_meta])* },
264            $name,
265            { $(#[$table_meta])* },
266            $table,
267            $table {
268                $($(#[$variant_meta])* $variant $(= $value)? as $field),+
269            }
270        }
271
272        $crate::finite_set! {
273            @cursor
274            { $crate::finite_set!(@count $($variant),+) },
275            $name,
276            $cursor
277        }
278    };
279
280    (
281        $(#[$name_meta:meta])*
282        $name:ident,
283        $(#[$record_meta:meta])*
284        $record:ident,
285        $(#[$table_meta:meta])*
286        $table:ident,
287        {
288            $($(#[$variant_meta:meta])* $variant:ident $(= $value:tt)? as $field:ident),+ $(,)?
289        }
290    ) => {
291        $crate::finite_set! {
292            @record
293            { $crate::finite_set!(@count $($variant),+) },
294            { $(#[$name_meta])* },
295            $name,
296            { $(#[$record_meta])* },
297            $record,
298            { $(#[$table_meta])* },
299            $table,
300            {
301                $($(#[$variant_meta])* $variant $(= $value)? as $field),+
302            }
303        }
304    };
305
306    (
307        @table
308        $len:expr,
309        { $(#[$name_meta:meta])* },
310        $name:ident,
311        { $(#[$table_meta:meta])* },
312        $table:ident,
313        $table_expr:ident {
314            $($(#[$variant_meta:meta])* $variant:ident $(= $value:tt)? as $field:tt),+ $(,)?
315        }
316    ) => {
317        $(#[$name_meta])*
318        #[repr(u8)]
319        #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
320        pub enum $name {
321            $(
322                $(#[$variant_meta])*
323                $variant $(= $value)?
324            ),+
325        }
326
327        $(#[$table_meta])*
328        pub type $table<T = bool> = $crate::finite::Table<$name, T, $len>;
329
330        impl $crate::finite::FiniteSet<$len> for $name {
331            const ALL: [Self; $len] = [$(Self::$variant),+];
332        }
333
334        #[doc = concat!(stringify!($name), " Finite Set API.")]
335        impl $name {
336            pub const LEN: usize = $len;
337
338            pub const ALL: [Self; $len] = <Self as $crate::finite::FiniteSet<$len>>::ALL;
339
340            #[inline]
341            pub const fn name(self) -> &'static str {
342                match self {
343                    $(Self::$variant => $crate::finite_set!(@name $field)),+
344                }
345            }
346
347            #[inline]
348            pub const fn eq(self, other: Self) -> bool {
349                self as u8 == other as u8
350            }
351
352            #[inline]
353            pub const fn index(self) -> usize {
354                let mut index = 0;
355                while index < Self::ALL.len() {
356                    if Self::ALL[index] as u8 == self as u8 {
357                        return index;
358                    }
359                    index += 1;
360                }
361                unreachable!()
362            }
363
364            #[inline]
365            pub const fn from_index(index: u8) -> Option<Self> {
366                if index < Self::LEN as u8 {
367                    Some(Self::panicky_from_index(index))
368                } else {
369                    None
370                }
371            }
372
373            #[inline]
374            #[track_caller]
375            pub(crate) const fn panicky_from_index(index: u8) -> Self {
376                assert!(index < Self::LEN as u8);
377                Self::ALL[index as usize]
378            }
379
380            #[inline]
381            pub fn iter() -> impl Iterator<Item = Self> {
382                <Self as $crate::finite::FiniteSet<$len>>::iter()
383            }
384
385            #[inline]
386            pub fn iter_rev() -> impl Iterator<Item = Self> {
387                <Self as $crate::finite::FiniteSet<$len>>::iter_rev()
388            }
389        }
390
391        impl core::fmt::Display for $name {
392            #[inline]
393            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
394                f.write_str(self.name())
395            }
396        }
397
398        impl<T: Copy> $crate::finite::Table<$name, T, $len> {
399            #[inline]
400            pub const fn get(&self, key: $name) -> T {
401                self.all[key.index()]
402            }
403
404            #[inline]
405            pub const fn set(&mut self, key: $name, value: T) {
406                self.all[key.index()] = value;
407            }
408        }
409
410        impl<T> $crate::finite::Table<$name, T, $len> {
411            #[inline]
412            pub const fn get_ref(&self, key: $name) -> &T {
413                &self.all[key.index()]
414            }
415
416            #[inline]
417            pub const fn get_mut(&mut self, key: $name) -> &mut T {
418                &mut self.all[key.index()]
419            }
420        }
421
422        impl<T> core::ops::Index<$name> for $crate::finite::Table<$name, T, $len> {
423            type Output = T;
424
425            #[inline]
426            fn index(&self, key: $name) -> &T {
427                &self.all[key.index()]
428            }
429        }
430
431        impl<T> core::ops::IndexMut<$name> for $crate::finite::Table<$name, T, $len> {
432            #[inline]
433            fn index_mut(&mut self, key: $name) -> &mut T {
434                &mut self.all[key.index()]
435            }
436        }
437    };
438
439    (
440        @record
441        $len:expr,
442        { $(#[$name_meta:meta])* },
443        $name:ident,
444        { $(#[$record_meta:meta])* },
445        $record:ident,
446        { $(#[$table_meta:meta])* },
447        $table:ident,
448        {
449            $($(#[$variant_meta:meta])* $variant:ident $(= $value:tt)? as $field:ident),+ $(,)?
450        }
451    ) => {
452        $crate::finite_set! {
453            @table
454            $len,
455            { $(#[$name_meta])* },
456            $name,
457            { $(#[$table_meta])* },
458            $table,
459            $table {
460                $($(#[$variant_meta])* $variant $(= $value)? as $field),+
461            }
462        }
463
464        $(#[$record_meta])*
465        #[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
466        pub struct $record<T = bool> {
467            $(
468                $(#[$variant_meta])*
469                pub $field: T
470            ),+
471        }
472
473        impl<T: Copy> $record<T> {
474            #[inline]
475            pub const fn get(&self, key: $name) -> T {
476                *self.get_ref(key)
477            }
478        }
479
480        impl<T> $record<T> {
481            #[inline]
482            pub const fn get_ref(&self, key: $name) -> &T {
483                match key {
484                    $( $name::$variant => &self.$field ),+
485                }
486            }
487
488            #[inline]
489            pub const fn get_mut(&mut self, key: $name) -> &mut T {
490                match key {
491                    $( $name::$variant => &mut self.$field ),+
492                }
493            }
494        }
495
496        impl<T: $crate::finite::Empty> $crate::finite::Empty for $record<T> {
497            const EMPTY: Self = Self {
498                $( $field: T::EMPTY ),+
499            };
500
501            fn is_empty(&self) -> bool {
502                $( self.$field.is_empty() )&&+
503            }
504        }
505
506        impl<T> core::ops::Index<$name> for $record<T> {
507            type Output = T;
508
509            #[inline]
510            fn index(&self, key: $name) -> &T {
511                self.get_ref(key)
512            }
513        }
514
515        impl<T> core::ops::IndexMut<$name> for $record<T> {
516            #[inline]
517            fn index_mut(&mut self, key: $name) -> &mut T {
518                self.get_mut(key)
519            }
520        }
521    };
522
523    (
524        @cursor
525        $len:expr,
526        $name:ident,
527        $cursor:ident
528    ) => {
529        #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
530        pub struct $cursor {
531            next: u8,
532        }
533
534        impl $name {
535            #[inline]
536            pub const fn cursor() -> $cursor {
537                $cursor { next: 0 }
538            }
539        }
540
541        impl $cursor {
542            #[inline]
543            pub const fn next(&mut self) -> Option<$name> {
544                if self.done() {
545                    None
546                } else {
547                    let value = $name::panicky_from_index(self.next);
548                    self.next += 1;
549                    Some(value)
550                }
551            }
552
553            #[inline]
554            pub const fn skip(&mut self, n: u8) -> bool {
555                let Some(next) = self.next.checked_add(n) else {
556                    return false;
557                };
558                if next > $len as u8 {
559                    return false;
560                }
561                self.next = next;
562                true
563            }
564
565            #[inline]
566            pub const fn done(self) -> bool {
567                self.next >= $len as u8
568            }
569        }
570    };
571
572    (@count $($variant:ident),+) => {
573        <[()]>::len(&[$($crate::finite_set!(@unit $variant)),+])
574    };
575
576    (@unit $variant:ident) => {
577        ()
578    };
579
580    (@name $field:ident) => {
581        stringify!($field)
582    };
583
584    (@name $field:literal) => {
585        $field
586    };
587}
588
589#[macro_export]
590/// Loops over all values of a generated finite set in const contexts.
591///
592/// This is a small const-friendly replacement for `for value in Type::ALL`.
593macro_rules! finite_for {
594    ($value:ident in $set:ty $body:block) => {{
595        let all = <$set>::ALL;
596        let mut index = 0usize;
597        while index < all.len() {
598            let $value = all[index];
599            $body
600            index += 1;
601        }
602    }};
603}
604
605// TODO: If we ever need it, add a finite_map! and finite_find! etc.