frclib_core/structure/
mod.rs

1//! This module contains an implementation of [WPIlib struct spec](https://github.com/wpilibsuite/allwpilib/blob/main/wpiutil/doc/struct.adoc)
2//! for rust and a macro to generate the trait implementation for a given struct.
3
4#[cfg(test)]
5mod test;
6
7mod prims;
8
9// use logos::Logos;
10
11use std::{hash::Hash, io::Cursor};
12
13pub use inventory;
14
15/// A description of a structure, used for serialization and deserialization
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17pub struct FrcStructDesc {
18    /// A function that returns the schema of the structure,
19    /// this is needed because the schema cannot be made in a const context
20    pub schema_supplier: fn() -> String,
21    /// The type of the structure, typically the name of the rust type
22    pub type_str: &'static str,
23    /// The size of the structure in bytes
24    pub size: usize,
25}
26
27impl Hash for FrcStructDesc {
28    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
29        self.type_str.hash(state);
30    }
31}
32
33inventory::collect!(FrcStructDesc);
34
35/// A global database of structure descriptions
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
37pub struct FrcStructDescDB;
38
39impl FrcStructDescDB {
40    /// Adds a structure description to the global database,
41    /// this is a runtime equivalent of the [`inventory::submit!`] macro.
42    #[cold]
43    pub fn add(desc: FrcStructDesc) {
44        if Self::contains_type(desc.type_str) {
45            return;
46        }
47        let static_desc_ref = Box::leak(Box::new(desc));
48        let node = inventory::Node {
49            value: static_desc_ref,
50            next: ::inventory::core::cell::UnsafeCell::new(::inventory::core::option::Option::None),
51        };
52        unsafe { inventory::ErasedNode::submit(node.value, Box::leak(Box::new(node))) }
53    }
54
55    /// Adds a structure description to the global database,
56    /// this is a runtime equivalent of the [`inventory::submit!`] macro.
57    #[cold]
58    pub fn add_ref(desc: &'static FrcStructDesc) {
59        if Self::contains_type(desc.type_str) {
60            return;
61        }
62        let node = inventory::Node {
63            value: desc,
64            next: ::inventory::core::cell::UnsafeCell::new(::inventory::core::option::Option::None),
65        };
66        unsafe { inventory::ErasedNode::submit(node.value, Box::leak(Box::new(node))) }
67    }
68
69    /// Checks if the global database contains a structure description for a given type
70    #[must_use]
71    pub fn contains_type(type_str: &str) -> bool {
72        inventory::iter::<FrcStructDesc>
73            .into_iter()
74            .any(|desc| desc.type_str == type_str)
75    }
76
77    /// Gets a structure description from the global database for a given type,
78    /// returns None if the type is not found
79    #[must_use]
80    pub fn get(type_str: &str) -> Option<&'static FrcStructDesc> {
81        inventory::iter::<FrcStructDesc>
82            .into_iter()
83            .find(|desc| desc.type_str == type_str)
84    }
85}
86
87pub use frclib_structure_macros::FrcStructure;
88
89/// A trait that allows serialization and deserialization of arbitrary structures
90/// to and from a [``FrcValue``](crate::value::FrcValue)
91pub trait FrcStructure
92where
93    Self: Sized + Copy,
94{
95    /// The schema of the structure,
96    /// this is a string that describes the format of the structure
97    const SCHEMA_SUPPLIER: fn() -> String;
98    /// The type of the structure as a string,
99    /// this is typically the name of the rust type
100    const TYPE: &'static str;
101    /// The size of the structure in bytes
102    const SIZE: usize;
103    /// An [``FrcStructDesc``](crate::structure::FrcStructDesc) that describes the structure,
104    /// it's composed of [`SIZE`](crate::structure::FrcStructure::SIZE),
105    /// [`TYPE`](crate::structure::FrcStructure::TYPE),
106    /// and [`SCHEMA_SUPPLIER`](crate::structure::FrcStructure::SCHEMA_SUPPLIER)
107    const DESCRIPTION: FrcStructDesc = FrcStructDesc {
108        schema_supplier: Self::SCHEMA_SUPPLIER,
109        type_str: Self::TYPE,
110        size: Self::SIZE,
111    };
112
113    /// Packs the structure into a buffer
114    fn pack(&self, buffer: &mut Vec<u8>);
115
116    /// Unpacks the structure from a buffer
117    fn unpack(buffer: &mut Cursor<&[u8]>) -> Self;
118
119    #[must_use]
120    #[doc(hidden)]
121    fn format_field(field: &str) -> String {
122        format!("{} {}", Self::TYPE, field)
123    }
124}
125
126/// A way of defining any number of same typed [``FrcStructure``]s
127/// in a single binary heap.
128///
129/// The type information and struct count is also coupled with the binary data
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct FrcStructureBytes {
132    /// The description of the structure types and layout
133    pub desc: &'static FrcStructDesc,
134    /// The number of structs packed into `data`
135    pub count: usize,
136    /// The binary data of the structs
137    pub data: Box<[u8]>,
138}
139impl FrcStructureBytes {
140    /// Creates a new [``FrcStructureBytes``] from a description, count, and data
141    #[must_use]
142    pub fn from_parts(desc: &'static FrcStructDesc, count: usize, data: Box<[u8]>) -> Self {
143        Self { desc, count, data }
144    }
145}
146
147/// A set length string of characters
148pub type FixedString<const N: usize> = [char; N];
149
150// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
151// pub(crate) enum StructureFieldTypes {
152//     Bool(usize),
153//     Char(usize),
154//     Int8(usize),
155//     Int16(usize),
156//     Int32(usize),
157//     Int64(usize),
158//     UInt8(usize),
159//     UInt16(usize),
160//     UInt32(usize),
161//     UInt64(usize),
162//     Float32(usize),
163//     Float64(usize),
164// }
165
166// impl StructureFieldTypes {
167//     #[allow(clippy::match_same_arms)]
168//     const fn base_size(&self) -> usize {
169//         match self {
170//             Self::Bool(_) => 1,
171//             Self::Char(_) => 1,
172//             Self::Int8(_) => 1,
173//             Self::Int16(_) => 2,
174//             Self::Int32(_) => 4,
175//             Self::Int64(_) => 8,
176//             Self::UInt8(_) => 1,
177//             Self::UInt16(_) => 2,
178//             Self::UInt32(_) => 4,
179//             Self::UInt64(_) => 8,
180//             Self::Float32(_) => 4,
181//             Self::Float64(_) => 8,
182//         }
183//     }
184
185//     const fn count(&self) -> usize {
186//         match self {
187//             Self::Bool(c)
188//             | Self::Char(c)
189//             | Self::Int8(c)
190//             | Self::Int16(c)
191//             | Self::Int32(c)
192//             | Self::Int64(c)
193//             | Self::UInt8(c)
194//             | Self::UInt16(c)
195//             | Self::UInt32(c)
196//             | Self::UInt64(c)
197//             | Self::Float32(c)
198//             | Self::Float64(c) => *c,
199//         }
200//     }
201
202//     const fn size(&self) -> usize {
203//         self.base_size() * self.count()
204//     }
205
206//     fn from_type(type_name: &str, count: usize) -> Option<Self> {
207//         match type_name {
208//             "bool" => Some(Self::Bool(count)),
209//             "char" => Some(Self::Char(count)),
210//             "int8" => Some(Self::Int8(count)),
211//             "int16" => Some(Self::Int16(count)),
212//             "int32" => Some(Self::Int32(count)),
213//             "int64" => Some(Self::Int64(count)),
214//             "uint8" => Some(Self::UInt8(count)),
215//             "uint16" => Some(Self::UInt16(count)),
216//             "uint32" => Some(Self::UInt32(count)),
217//             "uint64" => Some(Self::UInt64(count)),
218//             "float" | "float32" => Some(Self::Float32(count)),
219//             "double" | "float64" => Some(Self::Float64(count)),
220//             _ => None,
221//         }
222//     }
223// }
224
225// #[derive(Default, Debug, Clone, PartialEq)]
226// pub(crate) enum LexingError {
227//     ParseNumberError,
228//     EnumVariantError,
229//     #[default]
230//     Other,
231// }
232// impl From<std::num::ParseIntError> for LexingError {
233//     fn from(_: std::num::ParseIntError) -> Self {
234//         Self::ParseNumberError
235//     }
236// }
237
238// #[derive(logos::Logos, Debug, PartialEq, Eq, PartialOrd, Ord)]
239// #[logos(error = LexingError)]
240// #[logos(skip r"[ \t\n\f]+")]
241// pub(crate) enum Token<'a> {
242//     #[regex(
243//         r"bool|char|int8|int16|int32|int64|uint8|uint16|uint32|uint64|float32|float64|float|double",
244//         |lex| lex.slice(), priority = 3)]
245//     TypeName(&'a str),
246
247//     #[token("enum")]
248//     EnumKeyword,
249
250//     #[regex(
251//         r"[-a-zA-Z_][a-zA-Z0-9_-]*=-?[0-9]+",
252//         |lex| {
253//             let split = lex.slice().split('=').collect::<Vec<_>>();
254//             Ok::<_, LexingError>((
255//                 *split.first().ok_or(LexingError::EnumVariantError)?,
256//                 split.get(1).ok_or(LexingError::EnumVariantError)?.parse::<i8>()?
257//             ))
258//         }, priority = 3)]
259//     EnumVariant((&'a str, i8)),
260
261//     #[regex(r"[0-9]+", |lex| lex.slice().parse(), priority = 2)]
262//     Integer(u32),
263
264//     #[regex(r"[-a-zA-Z_][a-zA-Z0-9_-]*", |lex| lex.slice())]
265//     Ident(&'a str),
266
267//     #[token("{")]
268//     OpenBrace,
269//     #[token("}")]
270//     CloseBrace,
271//     #[token("[")]
272//     OpenBracket,
273//     #[token("]")]
274//     CloseBracket,
275//     #[token(",")]
276//     Comma,
277//     #[token(";")]
278//     Semicolon,
279//     #[token(":")]
280//     Colon,
281// }
282
283// #[allow(dead_code)]
284// pub(crate) fn parse_schema_toplevel(
285//     schema: &str,
286// ) -> Vec<(String, usize, StructureFieldTypes)> {
287//     parse_schema(schema, "", 0)
288// }
289
290// #[allow(dead_code)]
291// pub(crate) fn parse_schema(
292//     schema: &str,
293//     prefix: &str,
294//     offset: usize,
295// ) -> Vec<(String, usize, StructureFieldTypes)> {
296//     let lexer = Token::lexer(schema);
297//     let tokens_collect: Vec<_> = lexer.collect();
298//     for tok in &tokens_collect {
299//         if tok.is_err() {
300//             return vec![];
301//         }
302//     }
303//     let tokens = tokens_collect.into_iter();
304//     let mut cursor = offset;
305//     tokens
306//         .map(|token| token.expect("Lexing Token Slipped Past"))
307//         .filter(|token| {
308//             matches!(
309//                 token,
310//                 Token::Ident(_) | Token::Integer(_) | Token::TypeName(_) | Token::Semicolon
311//             )
312//         })
313//         .collect::<Vec<_>>()
314//         .split(|token| token == &Token::Semicolon)
315//         .filter_map(|field_tokens| {
316//             if field_tokens.len() < 2 || field_tokens.len() > 3 {
317//                 return None;
318//             }
319
320//             let Token::Ident(ident) = field_tokens[1] else {
321//                 return None;
322//             };
323
324//             match field_tokens[0] {
325//                 Token::Ident(sub_struct) => {
326//                     if let Some(desc) = FrcStructDescDB::get(sub_struct) {
327//                         let ret = parse_schema(
328//                             &(desc.schema_supplier)(),
329//                             format!("{ident}.").as_str(),
330//                             cursor,
331//                         );
332//                         cursor += desc.size;
333//                         return Some(ret);
334//                     }
335//                 }
336//                 Token::TypeName(type_name) => {
337//                     let count = match field_tokens.get(2) {
338//                         Some(Token::Integer(int)) => *int as usize,
339//                         _ => 1,
340//                     };
341//                     if let Some(stype) = StructureFieldTypes::from_type(type_name, count) {
342//                         let ret = vec![(format!("{prefix}{ident}"), cursor, stype)];
343//                         cursor += stype.size();
344//                         return Some(ret);
345//                     }
346//                 }
347//                 _ => {}
348//             }
349//             None::<Vec<(String, usize, StructureFieldTypes)>>
350//         })
351//         .flatten()
352//         .collect()
353// }
354
355// pub struct DynamicStructure {
356//     desc: &'static FrcStructDesc,
357//     buffer: BytesMut,
358//     _map: HashMap<String, (usize, StructureFieldTypes), fxhash::FxBuildHasher>,
359// }
360
361// impl DynamicStructure {
362//     pub fn try_new(desc: &'static FrcStructDesc, buffer: BytesMut) -> Result<Self, String> {
363//         if buffer.len() != desc.size {
364//             return Err(format!(
365//                 "Buffer size ({}) does not match structure size ({})",
366//                 buffer.len(),
367//                 desc.size
368//             ));
369//         }
370//         let mut map = HashMap::with_hasher(fxhash::FxBuildHasher::default());
371//         for field in parse_schema_toplevel(desc.schema) {
372//             map.insert(field.0, (field.1, field.2));
373//         }
374//         Ok(DynamicStructure {
375//             desc,
376//             buffer,
377//             _map: map,
378//         })
379//     }
380
381//     pub fn description(&self) -> &'static FrcStructDesc {
382//         self.desc
383//     }
384
385//     pub fn update(&mut self, new: Box<Bytes>) {
386//         debug_assert!(new.len() == self.buffer.len());
387//         self.buffer[..].copy_from_slice(&new[..]);
388//     }
389// }