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// }