From d104e62bf3c84480d9ed9bf68bf9ec66f6107b3a Mon Sep 17 00:00:00 2001 From: Wataru Otsubo Date: Sat, 1 Feb 2025 19:33:51 +0900 Subject: [PATCH] new(generator): implement register codegen (codegen_register) & fix interpolation bug also added a lot of docs, since register codegen is quite complex (This became too large commit...) --- Cargo.lock | 17 ++ Cargo.toml | 2 + src/generator.rs | 204 ++++++++++++++- src/generator/codegen_register.rs | 412 ++++++++++++++++++++++++++++++ src/lib.rs | 7 + src/main.rs | 2 +- src/parser.rs | 2 + src/types.rs | 2 +- 8 files changed, 633 insertions(+), 15 deletions(-) create mode 100644 src/generator/codegen_register.rs diff --git a/Cargo.lock b/Cargo.lock index 5ef333e..c18c3b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "endcap-sl-software-ri-generator" version = "0.1.0" @@ -80,10 +86,12 @@ dependencies = [ "anyhow", "env_logger", "heck", + "itertools", "log", "proc-macro2", "quote", "roxmltree", + "syn", "thiserror", ] @@ -128,6 +136,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "log" version = "0.4.25" diff --git a/Cargo.toml b/Cargo.toml index 6328c5f..755bd1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,10 @@ path = "src/lib.rs" anyhow = "1.0.95" env_logger = "0.11.6" heck = "0.5" +itertools = "0.14" log = "0.4" proc-macro2 = "1.0.93" quote = "1.0" roxmltree = "0.20" +syn = "2.0.96" thiserror = "2.0" diff --git a/src/generator.rs b/src/generator.rs index 47ebd86..d5deb1d 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,19 +1,126 @@ //! Generate register interface rust code from types in [`crate::types`]. +//! +//! root: [`CodeGen`] use crate::{ type_traits::GetName, - types::{Block, Module, ModuleBlockElements}, + types::{Block, Module, ModuleBlockElements, Register}, }; use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::TokenStream; use quote::quote; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CodeGenError { + #[error("tokenization error: {0}")] + TokenizeError(#[from] syn::Error), +} + +mod util { + use crate::types::DataType; + + use super::CodeGenError; + + pub(super) fn parse_to_ident(s: &str) -> Result { + Ok(syn::parse_str(s)?) + } + + pub(super) fn parse_to_literal(s: &str) -> Result { + Ok(syn::parse_str(s)?) + } + + // currently only U32 is used, so `dead_code` for Debug, PartialEq + #[allow(dead_code)] + #[derive(Debug, PartialEq)] + pub(crate) enum RustUxTypes { + Bool, + U8, + U16, + U32, + } + + impl RustUxTypes { + /// Derive appropriate rust types for `mask`ed value. + pub(super) fn from_mask(mask: u32) -> RustUxTypes { + match 32 - mask.leading_zeros() - mask.trailing_zeros() { + 0 => panic!("mask cannot be 0"), + 1 => RustUxTypes::Bool, + x if 1 < x && x <= 8 => RustUxTypes::U8, + x if 8 < x && x <= 16 => RustUxTypes::U16, + x if 16 < x && x <= 32 => RustUxTypes::U32, + _ => panic!("supposed not to be reachable"), + } + } + + pub(super) fn to_rust_type_token(&self) -> proc_macro2::Ident { + match self { + RustUxTypes::Bool => parse_to_ident("bool").unwrap(), + RustUxTypes::U8 => parse_to_ident("u8").unwrap(), + RustUxTypes::U16 => parse_to_ident("u16").unwrap(), + RustUxTypes::U32 => parse_to_ident("u32").unwrap(), + } + } + } + + impl From<&DataType> for RustUxTypes { + fn from(value: &DataType) -> Self { + match value { + DataType::D32 => Self::U32, + } + } + } + + impl DataType { + fn to_rust_type_token(&self) -> proc_macro2::Ident { + match self { + DataType::D32 => proc_macro2::Ident::new("u32", proc_macro2::Span::call_site()), + } + } + } + + #[cfg(test)] + mod test { + use super::RustUxTypes; + + #[test] + fn rustuxtypes_from_mask() { + assert_eq!(RustUxTypes::from_mask(0x1), RustUxTypes::Bool); + assert_eq!(RustUxTypes::from_mask(0x20), RustUxTypes::Bool); + assert_eq!(RustUxTypes::from_mask(0x4000), RustUxTypes::Bool); + assert_eq!(RustUxTypes::from_mask(0x80_0000), RustUxTypes::Bool); + assert_eq!(RustUxTypes::from_mask(0x100_0000), RustUxTypes::Bool); + + assert_eq!(RustUxTypes::from_mask(0x0300_0000), RustUxTypes::U8); + assert_eq!(RustUxTypes::from_mask(0x0000_01e0), RustUxTypes::U8); + + assert_eq!(RustUxTypes::from_mask(0x0000_01f0), RustUxTypes::U8); + assert_eq!(RustUxTypes::from_mask(0x000f_f000), RustUxTypes::U8); + + assert_eq!(RustUxTypes::from_mask(0x0fff_0000), RustUxTypes::U16); + assert_eq!(RustUxTypes::from_mask(0x0f0f_0000), RustUxTypes::U16); + assert_eq!(RustUxTypes::from_mask(0x010f_8000), RustUxTypes::U16); + + assert_eq!(RustUxTypes::from_mask(0xffff_f000), RustUxTypes::U32); + assert_eq!(RustUxTypes::from_mask(0x1fff_ff00), RustUxTypes::U32); + } + + #[test] + fn rustuxtypes_to_token() { + assert_eq!( + RustUxTypes::U8.to_rust_type_token(), + proc_macro2::Ident::new("u8", proc_macro2::Span::call_site()) + ) + } + } +} pub trait CodeGen { - fn generate_register_interface(self) -> proc_macro2::TokenStream; + fn generate_register_interface(self) -> Result; } impl CodeGen for Module { - fn generate_register_interface(self) -> proc_macro2::TokenStream { + fn generate_register_interface(self) -> Result { let mut out = TokenStream::new(); if !self.elements_bitstring.is_empty() { todo!("bitstring generation is not yet implemented") @@ -27,15 +134,15 @@ impl CodeGen for Module { out.extend(child); } - out + Ok(out) } } impl CodeGen for ModuleBlockElements { - fn generate_register_interface(self) -> proc_macro2::TokenStream { + fn generate_register_interface(self) -> Result { match self { ModuleBlockElements::Block(block) => block.generate_register_interface(), - ModuleBlockElements::Register(register) => todo!(), + ModuleBlockElements::Register(register) => register.generate_register_interface(), ModuleBlockElements::Memory(memory) => todo!(), ModuleBlockElements::Fifo(fifo) => todo!(), } @@ -43,10 +150,10 @@ impl CodeGen for ModuleBlockElements { } impl CodeGen for Block { - fn generate_register_interface(self) -> proc_macro2::TokenStream { - let snake_case_name = self.name.to_snake_case(); - let upper_camel_name = self.name.to_upper_camel_case(); - let addr = format!("0x{:x}", self.addr); + fn generate_register_interface(self) -> Result { + let snake_case_name = util::parse_to_ident(&self.name.to_snake_case())?; + let upper_camel_name = util::parse_to_ident(&self.name.to_upper_camel_case())?; + let addr = util::parse_to_literal(&format!("0x{:x}", self.addr))?; let desc = self.desc.unwrap_or("".to_string()); let accessor_methods = { @@ -64,7 +171,7 @@ impl CodeGen for Block { out }; - + let child_mods = { let mut out = TokenStream::new(); for child_mod in self.elements.into_iter() { @@ -75,7 +182,7 @@ impl CodeGen for Block { out }; - quote! { + Ok(quote! { pub mod #snake_case_name { #[doc = #desc] @@ -105,6 +212,77 @@ impl CodeGen for Block { #accessor_methods } } - } + }) + } +} + +mod codegen_register; + +impl CodeGen for Register { + fn generate_register_interface(self) -> Result { + let snake_case_name = util::parse_to_ident(&self.name.to_snake_case())?; + let upper_camel_name = util::parse_to_ident(&self.name.to_upper_camel_case())?; + let reg_name = util::parse_to_ident(&format!("Reg{upper_camel_name}"))?; + let addr = util::parse_to_literal(&format!("0x{:x}", self.addr))?; + + let (code_t_def, type_t, type_ux): ( + proc_macro2::TokenStream, + proc_macro2::Ident, + proc_macro2::Ident, + ) = codegen_register::reg_type_def(&self, &upper_camel_name)?; + + let code_reg_def: proc_macro2::TokenStream = todo!(); + + Ok(quote! { + pub mod #snake_case_name { + use std::marker::PhantomData; + + use crate::register_spec::{DataConversionError, Modifiable, Readable, RegisterSpec, Writable}; + + const OFFSET: usize = #addr; + + pub struct #reg_name<'a> { + mem_ptr: *mut u32, + _marker: PhantomData<&'a mut super::Debug<'a>>, + } + + impl #reg_name<'_> { + pub(crate) fn new(parent_ptr: *mut u32) -> Self { + #reg_name { + mem_ptr: unsafe { parent_ptr.add(OFFSET) }, + _marker: PhantomData, + } + } + } + + impl RegisterSpec for #reg_name<'_> { + type Ux = u32; + type T = SlId; + + fn as_ptr(&self) -> *mut Self::Ux { + self.mem_ptr + } + } + impl Readable for #reg_name<'_> {} + impl Writable for #reg_name<'_> {} + impl Modifiable for #reg_name<'_> {} + + #[derive(Debug, Clone, Copy, Default)] + pub struct SlId(pub u32); + impl TryFrom for SlId { + type Error = DataConversionError; + + fn try_from(value: u32) -> Result { + let mask = 0x0000003f; + Ok(SlId(value & mask)) + } + } + impl From for u32 { + fn from(val: SlId) -> Self { + val.0 + } + } + } + }) } } diff --git a/src/generator/codegen_register.rs b/src/generator/codegen_register.rs new file mode 100644 index 0000000..db5deee --- /dev/null +++ b/src/generator/codegen_register.rs @@ -0,0 +1,412 @@ +//! Generator for [`Register`]. +//! The entry point is [`reg_type_def`]. + +use heck::{ToShoutySnekCase, ToSnakeCase}; +use itertools::Itertools; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +use crate::types::{DataType, Field, MultipleParams, Register}; + +use super::{ + util::{self, RustUxTypes}, + CodeGenError, +}; + +/// Generate underlying `T` and its implementation for `RegisterSpec`. +/// +/// # Returns +/// tuple of +/// - code of `T` definition and its impls +/// - [`Ident`] of `T` +/// - [`Ident`] of `Ux` +/// +/// # Cases +/// - `T` == `Ux` => [`reg_type_def_simple`] +/// - `T` is masked `Ux` => [`reg_type_def_masked`] +/// - `T` has 1+ fields => [`reg_type_def_with_field`] +pub(super) fn reg_type_def( + reg: &Register, + upper_camel_name: &Ident, +) -> Result<(TokenStream, Ident, Ident), CodeGenError> { + Ok(match reg.elements.is_empty() { + true => match reg.mask { + Some(mask) => reg_type_def_masked(®.r#type, mask, upper_camel_name), + None => reg_type_def_simple(®.r#type), + }, + false => reg_type_def_with_field(®.r#type, ®.elements, upper_camel_name)?, + }) +} + +/// Where `T` == `Ux`. +/// +/// No `T` def nor impl are required. +/// +/// # Returns +/// same as [`reg_type_def`] +fn reg_type_def_simple(basetype: &DataType) -> (TokenStream, Ident, Ident) { + let type_t_ux = { + let x: RustUxTypes = basetype.into(); + x.to_rust_type_token() + }; + + let out = quote! {}; + (out, type_t_ux.clone(), type_t_ux) +} + +/// Where `T` is masked `Ux`. +/// +/// `T` is a one-length "tuple struct" `T(pub Ux)`. +/// `impl TryFrom for T` and `impl From for Ux` are included, +/// but without `impl T`. +/// +/// # Returns +/// same as [`reg_type_def`] +fn reg_type_def_masked( + basetype: &DataType, + mask: u32, + upper_camel_name: &Ident, +) -> (TokenStream, Ident, Ident) { + let type_ux = { + let x: RustUxTypes = basetype.into(); + x.to_rust_type_token() + }; + + let out = quote! { + #[derive(Debug, Clone, Copy, Default)] + pub struct #upper_camel_name(pub #type_ux); + impl TryFrom<#type_ux> for #upper_camel_name { + type Error = DataConversionError<#type_ux, Self>; + + fn try_from(value: #type_ux) -> Result { + let mask = #mask; + Ok(SlId(value & mask)) + } + } + impl From<#upper_camel_name> for #type_ux { + fn from(value: #upper_camel_name) -> Self { + value.0 + } + } + + }; + + (out, upper_camel_name.clone(), type_ux) +} + +/// Where `T` has fields. +/// +/// `T` is a "struct struct" which has a single field `inner: Ux`. +/// Also, a bunch of "mask" constants and getter/setter are defined for each fields. +/// +/// # Returns +/// same as [`reg_type_def`] +/// +/// # Field generation +/// This function calls [`generate_field`] for each [`Field`] entries to generate field definitions. +/// See the doc of `generate_field` for more detail +fn reg_type_def_with_field( + basetype: &DataType, + fields: &[Field], + upper_camel_name: &Ident, +) -> Result<(TokenStream, Ident, Ident), CodeGenError> { + let type_ux = { + let x: RustUxTypes = basetype.into(); + x.to_rust_type_token() + }; + + let (code_masks, code_getters, code_setters): ( + Vec, + Vec, + Vec, + ) = fields + .iter() + .map(|field| generate_field(field, basetype)) + .process_results(|iter| iter.multiunzip())?; + + let out = quote! { + #(#code_masks)* + + #[derive(Debug, Clone, Copy, Default)] + pub struct #upper_camel_name { + inner: #type_ux, + } + + impl GtyDelayBank123 { + #(#code_getters)* + + #(#code_setters)* + } + impl TryFrom<#type_ux> for #upper_camel_name { + type Error = DataConversionError<#type_ux, Self>; + + fn try_from(value: #type_ux) -> Result { + Ok(Self { inner: value }) + } + } + impl From for u32 { + fn from(value: #upper_camel_name) -> Self { + value.inner + } + } + }; + + Ok((out, upper_camel_name.clone(), type_ux)) +} + +/// Generate code for each field, which consists of these three. +/// 1. mask definition +/// 2. getter method +/// 3. setter method +/// +/// # Cases +/// This function is separated into two cases. +/// See each function docs for more detail. +/// +/// 1. field is not "multiple" => [`generate_single_field`] +/// 2. field is "multiple" => [`generate_multiple_field`] +fn generate_field( + field: &Field, + basetype: &DataType, +) -> Result<(TokenStream, TokenStream, TokenStream), CodeGenError> { + let mask_name = util::parse_to_ident(&format!("{}_MASK", field.name.TO_SHOUTY_SNEK_CASE()))?; + let base_type = util::RustUxTypes::from(basetype).to_rust_type_token(); + + let snake_case_name = util::parse_to_ident(&field.name.to_snake_case())?; + let field_type = util::RustUxTypes::from_mask(field.mask); + + let (code_mask, code_getter, code_setter) = match &field.multiple { + Some(multiple_params) => generate_multiple_field( + mask_name, + base_type, + field.mask, + field_type, + snake_case_name, + multiple_params, + ), + None => generate_single_field( + mask_name, + base_type, + field.mask, + field_type, + snake_case_name, + ), + }; + + Ok((code_mask, code_getter, code_setter)) +} + +/// Generate "single" field definition (mask, getter, setter). +/// +/// # Cases +/// This function is separated into two cases. +/// +/// - field is `bool` => [`generate_single_bool_field`] +/// - field is `u8`/`u16`/`u32` => [`generate_single_ux_field`] +/// +/// For the details of types of register/field, see [`DataType`]. +/// Note that in both cases, mask definitions are the same. +fn generate_single_field( + mask_name: Ident, + base_type: Ident, + mask_val: u32, + field_type: RustUxTypes, + snake_case_name: Ident, +) -> (TokenStream, TokenStream, TokenStream) { + let mask_val = util::parse_to_literal(&format!("0x{:x}", mask_val)).unwrap(); + let code_mask = quote! { + const #mask_name: #base_type = #mask_val; + }; + let (code_getter, code_setter) = match field_type { + RustUxTypes::Bool => generate_single_bool_field(mask_name, snake_case_name), + RustUxTypes::U8 | RustUxTypes::U16 | RustUxTypes::U32 => { + generate_single_ux_field(mask_name, base_type, snake_case_name, field_type) + } + }; + + (code_mask, code_getter, code_setter) +} + +/// Generate bool "single" field definition (getter, setter). +fn generate_single_bool_field( + mask_name: Ident, + snake_case_name: Ident, +) -> (TokenStream, TokenStream) { + let setter_name = util::parse_to_ident(&format!("set_{}", snake_case_name)).unwrap(); + let code_getter = quote! { + pub fn #snake_case_name(&self) -> bool { + (self.inner & #mask_name) == #mask_name + } + }; + let code_setter = quote! { + pub fn #setter_name(self, val: bool) -> Self { + let mut inner = self.inner; + if val { + inner |= #mask_name + } else { + inner &= !#mask_name + }; + Self { inner } + } + }; + (code_getter, code_setter) +} + +/// Generate u8/u16/u32 "single" field definition (getter, setter). +fn generate_single_ux_field( + mask_name: Ident, + base_type: Ident, + snake_case_name: Ident, + field_type: RustUxTypes, +) -> (TokenStream, TokenStream) { + let field_type = field_type.to_rust_type_token(); + let setter_name = util::parse_to_ident(&format!("set_{}", snake_case_name)).unwrap(); + + let code_getter = quote! { + pub fn #snake_case_name(&self) -> #field_type { + const RIGHT_SHIFT: #base_type = #mask_name.trailing_zeros(); + ((self.inner & #mask_name) >> RIGHT_SHIFT) + .try_into() + .unwrap() + } + }; + let code_setter = quote! { + pub fn #setter_name(&self, val: #field_type) -> Self { + let update: #base_type = val & #mask_name; + let mut inner = self.inner; + inner &= !mask; + inner |= update; + Self { inner } + } + }; + (code_getter, code_setter) +} + +/// Generate "multiple" field definition (mask, getter, setter). +/// +/// # Cases +/// As "single" field cases, this function is separated into two cases; +/// +/// - fields are `bool` => [`generate_multiple_bool_field`] +/// - fields are `u8`/`u16`/`u32` => todo +fn generate_multiple_field( + mask_name: Ident, + base_type: Ident, + single_mask_val: u32, + single_field_type: RustUxTypes, + snake_case_name: Ident, + multiple_params: &MultipleParams, +) -> (TokenStream, TokenStream, TokenStream) { + let num_multiple = multiple_params.multiple; + let id_num_multiple = util::parse_to_ident(&num_multiple.to_string()).unwrap(); + let id_field_type = single_field_type.to_rust_type_token(); + let masks: Vec<_> = (0..multiple_params.multiple) + .map(|x| x * multiple_params.offset) + .map(|offset| single_mask_val << offset) + .map(|mask| util::parse_to_ident(&format!("0x{mask:x}")).unwrap()) + .collect(); + debug_assert_eq!(masks.len(), num_multiple.try_into().unwrap()); + let code_mask = quote! { + const #mask_name: [#id_field_type; #id_num_multiple] = [#(#masks),*]; + }; + + let (code_getter, code_setter) = match single_field_type { + RustUxTypes::Bool => { + generate_multiple_bool_field(mask_name, base_type, snake_case_name, masks.clone()) + } + RustUxTypes::U8 | RustUxTypes::U16 | RustUxTypes::U32 => generate_multiple_ux_field( + mask_name, + base_type, + snake_case_name, + single_field_type, + masks, + ), + }; + + (code_mask, code_getter, code_setter) +} + +/// Generate bool "multiple" field definition (getter, setter). +fn generate_multiple_bool_field( + mask_name: Ident, + base_type: Ident, + snake_case_name: Ident, + masks: Vec, +) -> (TokenStream, TokenStream) { + let num_multiple = masks.len(); + let elem_getter = masks.iter().enumerate().map(|(i, _mask)| { + quote! { + (self.inner & #mask_name[#i]) == #mask_name[#i] + } + }); + let code_getter = quote! { + pub fn #snake_case_name(&self) -> [bool; #num_multiple] { + [ + #(#elem_getter),* + ] + } + }; + + let setter_name = util::parse_to_ident(&format!("set_{}", snake_case_name)).unwrap(); + let code_setter = quote! { + pub fn #setter_name(self, val: [bool; #num_multiple]) -> Self { + let mask: #base_type = #mask_name.iter().sum(); + let update: #base_type = BANK121_GTY_CHANNEL_MASK + .iter() + .zip(val) + .filter_map(|(mask, val)| val.then_some(mask)) + .sum(); + let mut inner = self.inner; + inner &= !mask; + inner |= update; + Self { inner } + } + }; + + (code_getter, code_setter) +} + +/// Generate u8/u16/u32 "multiple" field definition (getter, setter). +fn generate_multiple_ux_field( + mask_name: Ident, + base_type: Ident, + snake_case_name: Ident, + single_field_type: RustUxTypes, + masks: Vec, +) -> (TokenStream, TokenStream) { + let field_type = single_field_type.to_rust_type_token(); + let num_multiple = masks.len(); + let elem_getter = masks.iter().enumerate().map(|(i, _mask)| { + quote! { + ((self.inner & #mask_name[#i]) + >> (#mask_name[#i].trailing_zeros())) + .try_into() + .unwrap() + } + }); + let code_getter = quote! { + pub fn #snake_case_name(&self) -> [#field_type; #num_multiple] { + [ + #(#elem_getter),* + ] + } + }; + + let setter_name = util::parse_to_ident(&format!("set_{}", snake_case_name)).unwrap(); + let code_setter = quote! { + pub fn #setter_name(&self, val: [#field_type; #num_multiple]) -> Self { + let mask: #base_type = #mask_name.iter().sum(); + let update: #base_type = #mask_name + .iter() + .zip(val) + .map(|(mask, val)| (#base_type::from(val)) << (mask.trailing_zeros())) + .sum(); + let mut inner = self.inner; + inner &= !mask; + inner |= update; + Self { inner } + } + }; + + (code_getter, code_setter) +} diff --git a/src/lib.rs b/src/lib.rs index 863641c..3c050d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,11 @@ //! Root document [`types::Module::from_xml_dom`] +//! +//! # Overview +//! +//! 1. Convert [`roxmltree::Document`] to register map respresented with types defined in +//! [`types`], filling missing parameters. See [`converter`]. +//! 2. Generate [`proc_macro2::TokenStream`] from register map produced in the previous step. See +//! [`generator`]. pub mod types; pub mod type_traits; diff --git a/src/main.rs b/src/main.rs index 79d45c4..170859b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ fn main() -> Result<()> { let register_map = types::Module::from_xml_dom(doc.root_element())?; println!("read: {:#?}", register_map); - println!("{}", register_map.generate_register_interface()); + println!("{}", register_map.generate_register_interface()?); Ok(()) } diff --git a/src/parser.rs b/src/parser.rs index 41882ef..4924fd7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,6 @@ //! Module for converting XML custom token string to rust types. +//! +//! Unlike, [`crate::converter`], this only has string "parser". use std::any; diff --git a/src/types.rs b/src/types.rs index 4bc205e..65cc783 100644 --- a/src/types.rs +++ b/src/types.rs @@ -126,7 +126,7 @@ pub enum AmodValues { USER2, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum DataType { D32, }