Skip to main content

edgefirst_tensor/
vocabulary.rs

1// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4//! One declaration per shared vocabulary, instead of four.
5//!
6//! `TensorMemory`, `PixelFormat`, and friends each need to agree across the
7//! Rust API, the wire protocol, the Python bindings, and the C header. Before
8//! this macro, each surface carried its own hand-written numbering of the
9//! same variants, and they only agreed by coincidence. `PyTensorMemory` is
10//! the live example: its Python enum has no explicit discriminants and a
11//! `#[cfg(unix)]` variant sits in the middle of the list, so every variant
12//! declared after it silently shifts by one on a non-unix build. Bridging
13//! Python and C by integer then hands back `Pbo` where `Mem` was requested,
14//! with no error at any layer.
15//!
16//! [`ef_vocabulary!`] closes that gap structurally: discriminants are
17//! mandatory and explicit in the single declaration, so a `cfg`-gated
18//! variant keeps its number reserved when it is compiled out, rather than
19//! renumbering everything after it. `code()` and the wire module's `const`s
20//! are generated from that one declaration, so the Rust enum, the wire
21//! encoding, and every other consumer read the same numbers by construction
22//! -- there is no second table to fall out of sync.
23
24/// Declare a vocabulary once, emit every surface from it.
25///
26/// Discriminants are MANDATORY and explicit. A `cfg`-gated variant therefore
27/// keeps its number reserved when absent, instead of shifting every variant
28/// after it -- which is how `PyTensorMemory` ended up platform-dependent.
29///
30/// Emits, for a vocabulary `V` with wire module `m`:
31/// - `pub enum V` with explicit `#[repr(u32)]` discriminants taken verbatim
32///   from the declaration.
33/// - `V::code(self) -> u32` (`const fn`) / `V::from_code(u32) -> Option<V>`,
34///   the wire numbering. `from_code` returns `None` for an unassigned code
35///   rather than guessing at the nearest variant. `code` being `const`
36///   lets a consumer that cannot call it directly (a `#[pyclass]`
37///   discriminant, which PyO3 requires to be a literal) instead pin its
38///   own literal to it with `const _: () = assert!(...)`.
39/// - `V::as_str(self) -> &'static str` / `V::from_str_code(&str) -> Option<V>`,
40///   the wire string form (not `Debug`, which is free to change).
41/// - `V::all() -> &'static [V]`, every variant in declaration order, so
42///   cross-surface consistency tests can be exhaustive instead of a sample.
43/// - `pub mod m { pub const CONST_NAME: u32 = ...; }`, one `const` per
44///   variant, for `const`-only consumers (cbindgen headers, match-free FFI
45///   code). This is the shape `protocol::dtype` / `protocol::format` /
46///   `protocol::kind` already use by hand. The `mod` clause accepts its own
47///   `$(#[meta])*` prefix (e.g. `#[doc(hidden)]`), the same as the `enum`
48///   clause does -- `m` has to be `pub` for a *different* module (typically
49///   `protocol`) to `pub use` it back out under its real, documented name,
50///   so `#[doc(hidden)]` is how a caller marks it as emission plumbing
51///   rather than a second, undocumented public API surface for the same
52///   vocabulary.
53///
54/// The wire `const`'s identifier is a separate, explicit field per variant
55/// rather than derived from the enum variant name. `DType`'s variants (`U8`,
56/// `I8`, ...) happen to already be valid `SCREAMING_SNAKE_CASE`, but
57/// `PixelFormat`'s do not -- `PlanarRgb` is the enum variant,
58/// `protocol::format::PLANAR_RGB` is the existing wire constant, and no
59/// mechanical transform of one produces the other without either an extra
60/// dependency for identifier-pasting-with-case-conversion or a hidden
61/// word-boundary guessing rule. Spelling both out keeps the same "explicit,
62/// not inferred" rule this macro already applies to discriminants.
63#[macro_export]
64macro_rules! ef_vocabulary {
65    (
66        $(#[$meta:meta])*
67        $vis:vis enum $name:ident {
68            $( $(#[$vmeta:meta])* $variant:ident = $code:expr, $text:literal, $const:ident ),+ $(,)?
69        }
70        $(#[$mmeta:meta])*
71        $modvis:vis mod $modname:ident;
72    ) => {
73        $(#[$meta])*
74        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75        #[repr(u32)]
76        $vis enum $name {
77            $( $(#[$vmeta])* $variant = $code ),+
78        }
79
80        impl $name {
81            /// The shared code for this variant. Identical on every surface.
82            ///
83            /// `const`, deliberately: a downstream binding that cannot
84            /// write `Variant = OtherEnum::Variant.code()` in a `#[pyclass]`
85            /// discriminant position (PyO3 requires a literal there) can
86            /// still pin that literal to this value with a
87            /// `const _: () = assert!(...)` -- a build-time check instead
88            /// of a hand-synced comment. See `PyTensorMemory`/`PyPixelFormat`
89            /// in `crates/python-common/src/tensor.rs` for the consumer.
90            pub const fn code(self) -> u32 { self as u32 }
91
92            /// Reject an unknown code rather than guessing at one.
93            pub fn from_code(code: u32) -> Option<Self> {
94                match code { $( $code => Some(Self::$variant), )+ _ => None }
95            }
96
97            /// The wire string form -- stable, unlike `Debug`.
98            pub fn as_str(self) -> &'static str {
99                match self { $( Self::$variant => $text, )+ }
100            }
101
102            /// Reject an unknown string rather than guessing at one.
103            pub fn from_str_code(s: &str) -> Option<Self> {
104                match s { $( $text => Some(Self::$variant), )+ _ => None }
105            }
106
107            /// Every variant, for exhaustive cross-surface tests.
108            pub fn all() -> &'static [Self] { &[ $( Self::$variant ),+ ] }
109        }
110
111        /// Wire codes for [`
112        #[doc = stringify!($name)]
113        /// `], one `const` per variant and identical to `.code()` for the
114        /// same variant -- both are generated from the same declared
115        /// literal here, so a `const`-only consumer (cbindgen, FFI) and
116        /// `code()` cannot independently drift apart.
117        $(#[$mmeta])*
118        $modvis mod $modname {
119            $( pub const $const: u32 = $code; )+
120        }
121    };
122}
123
124/// Demo vocabulary exercising [`ef_vocabulary!`] end to end.
125///
126/// `Beta`'s code (7) is deliberately non-contiguous with `Alpha`'s (0): it
127/// proves the discriminant comes from the declaration, not from position in
128/// the list. This module carries no real vocabulary -- [`crate::DType`] is
129/// converted onto the macro directly (as a proof the macro reproduces
130/// today's values unchanged); `PixelFormat` and `TensorMemory` follow in
131/// later tasks.
132#[doc(hidden)]
133pub mod vocabulary_demo {
134    crate::ef_vocabulary! {
135        pub enum Demo {
136            Alpha = 0, "alpha", ALPHA,
137            Beta = 7, "beta", BETA,
138        }
139        pub mod demo_code;
140    }
141}