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