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}