diff --git a/Cargo.lock b/Cargo.lock index dfaf1ae..dff8e1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,14 +73,26 @@ 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" dependencies = [ "anyhow", "env_logger", + "heck", + "itertools", "log", + "prettyplease", + "proc-macro2", + "quote", "roxmltree", + "syn", "thiserror", ] @@ -107,6 +119,12 @@ dependencies = [ "log", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "humantime" version = "2.1.0" @@ -119,6 +137,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" @@ -137,6 +164,16 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "prettyplease" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.93" diff --git a/Cargo.toml b/Cargo.toml index 8a02725..6bd8bcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,12 @@ path = "src/lib.rs" [dependencies] anyhow = "1.0.95" env_logger = "0.11.6" +heck = "0.5" +itertools = "0.14" log = "0.4" +prettyplease = "0.2" +proc-macro2 = "1.0.93" +quote = "1.0" roxmltree = "0.20" +syn = "2.0.96" thiserror = "2.0" diff --git a/src/converter.rs b/src/converter.rs index c9fcabe..426ba4d 100644 --- a/src/converter.rs +++ b/src/converter.rs @@ -1,4 +1,8 @@ -//! Convert DOM to register interface, complementing optional parameters. +//! Convert DOM to register interface defined in [`crate::types`], complementing optional parameters. +//! +//! root: [`Module::from_xml_dom`] +//! +//! error: [`DomConversionError`] use std::{num, str}; @@ -11,6 +15,7 @@ use crate::types::{ Value, }; +/// Possible errors in conversion, with positional information. #[derive(Debug, Error, PartialEq)] pub enum DomConversionError { #[error("attribute {attr} not found in element: {start} - {end}", start = pos.0, end = pos.1)] @@ -49,8 +54,11 @@ pub enum DomConversionError { param: &'static str, pos: (TextPos, TextPos), }, - #[error("other dom conversion error: {0}")] - OtherError(String), + #[error("other dom conversion error: {comment}: {start} - {end}", start = pos.0, end = pos.1)] + OtherError { + comment: &'static str, + pos: (TextPos, TextPos), + }, } impl DomConversionError { @@ -110,6 +118,15 @@ impl DomConversionError { found, } } + + fn other_error(comment: &'static str, node: Node) -> Self { + let range = node.range(); + let doc = node.document(); + Self::OtherError { + comment, + pos: (doc.text_pos_at(range.start), doc.text_pos_at(range.end)), + } + } } mod util { @@ -121,7 +138,11 @@ mod util { pub(crate) fn get_name(node: Node) -> Result { match node.attribute("name") { - Some(name) => Ok(name.to_string()), + Some(name) if !name.is_empty() => Ok(name.to_string()), + Some(_name) => Err(DomConversionError::other_error( + "name cannot be empty", + node, + )), None => Err(DomConversionError::attr_not_found("name", node)), } } @@ -281,8 +302,9 @@ impl Block { .next() { Some(s) => Ok(s.to_string()), - None => Err(DomConversionError::OtherError( - "decoder format is not yet fixed".to_string(), + None => Err(DomConversionError::other_error( + "decoder format is not yet fixed", + node, )), }?, }; @@ -323,6 +345,18 @@ impl Register { .map_err(|e| DomConversionError::parse_prefixed_u32_error(e, "addr", node))?, None => 0, }; + let r#type = util::get_type(node) + .transpose()? + .map_or_else( + || { + node.ancestors() + .filter_map(util::get_type) + .next() + .transpose() + }, + |x| Ok(Some(x)), + )? + .ok_or_else(|| DomConversionError::parameter_completion_error("type", node))?; let mask = node .attribute("mask") .map(|addr| { @@ -352,15 +386,41 @@ impl Register { .transpose()?; let desc = node.attribute("desc").map(str::to_string); - let children = node + let children: Vec<_> = node .children() .filter(|node| node.is_element() && node.tag_name().name().eq("field")) .map(Field::from_xml_dom) .collect::>()?; + // Validation + if mask.is_some() && !children.is_empty() { + return Err(DomConversionError::other_error( + "both mask and field are used in the same register", + node, + )); + } + if default.is_some() && !children.is_empty() { + return Err(DomConversionError::other_error( + "both default and field are used in the same register", + node, + )); + } + if let (Some(mask), Some(default)) = (mask, default) { + if default & !(mask) != 0 { + log::warn!( + "default value {} doesn't fit mask {}: {} - {}", + default, + mask, + node.document().text_pos_at(node.range().start), + node.document().text_pos_at(node.range().end) + ) + } + } + Ok(Register { name, addr, + r#type, mask, modf, multiple, @@ -372,13 +432,13 @@ impl Register { } impl Memory { - pub(crate) fn from_xml_dom(node: Node) -> Result { + pub(crate) fn from_xml_dom(_node: Node) -> Result { todo!() } } impl Fifo { - pub(crate) fn from_xml_dom(node: Node) -> Result { + pub(crate) fn from_xml_dom(_node: Node) -> Result { todo!() } } @@ -424,6 +484,7 @@ impl Field { .map(Value::from_xml_dom) .collect::>()?; + // Validation if let Some(default) = default { if default & !(mask) != 0 { log::warn!( diff --git a/src/generator.rs b/src/generator.rs new file mode 100644 index 0000000..a4c6d95 --- /dev/null +++ b/src/generator.rs @@ -0,0 +1,424 @@ +//! Generate register interface rust code from types in [`crate::types`]. +//! +//! root: [`Module::generate_code`] +//! +//! # For developers +//! Pass `--document-private-items` to see non-public items. + +use std::{ + collections::HashMap, + path::{self, PathBuf}, +}; + +use crate::{ + type_traits::GetName, + types::{Block, Module, ModuleBlockElements, Register}, +}; +use heck::{ToSnakeCase, ToUpperCamelCase}; +use itertools::Itertools; +use quote::quote; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CodeGenError { + #[error("tokenization(syn) error: {0}")] + SynError(#[from] syn::Error), + #[error("failed to create file: {0}")] + FilePathError(String), + #[error("parent is required for {module}")] + ParentMissing { module: &'static str }, + #[error("Unsupported structure: {}", name)] + UnsupportedStructure { name: &'static str }, +} + +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, + } + } + } + + #[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()) + ) + } + } +} + +impl Module { + pub fn generate_code(self) -> Result, CodeGenError> { + let files = self.generate_register_interface(None, None, HashMap::new())?; + Ok(files + .into_iter() + .map( + |(path, tokens)| -> Result<(PathBuf, syn::File), syn::Error> { + let file: syn::File = syn::parse2(tokens)?; + Ok((path, file)) + }, + ) + .process_results(|kv| HashMap::from_iter(kv))?) + } +} + +trait CodeGen { + /// `parent_name` in UpperCamelCase. + fn generate_register_interface( + self, + parent_name: Option, + parent_path: Option, + files: HashMap, + ) -> Result, CodeGenError>; +} + +impl CodeGen for Module { + fn generate_register_interface( + self, + _: Option, + _: Option, + mut files: HashMap, + ) -> std::result::Result, CodeGenError> { + if !self.elements_bitstring.is_empty() { + todo!("bitstring generation is not yet implemented") + } + + let child_mods = self + .elements_other + .iter() + .map(|e| { + util::parse_to_ident(&e.get_name().to_snake_case()).map(|child_name| { + quote! { + pub mod #child_name; + } + }) + }) + .collect::, _>>()?; + let out = quote! { + #(#child_mods)* + }; + files.insert(PathBuf::from("./register_interface.rs"), out); + + let ident_register_interface = util::parse_to_ident("RegisterInterface").unwrap(); + let register_interface_mod = PathBuf::from("register_interface"); + let files = self + .elements_other + .into_iter() + .try_fold(files, |files, e| { + e.generate_register_interface( + Some(ident_register_interface.clone()), + Some(register_interface_mod.clone()), + files, + ) + })?; + + Ok(files) + } +} + +impl CodeGen for ModuleBlockElements { + fn generate_register_interface( + self, + parent_name: Option, + parent_path: Option, + files: HashMap, + ) -> Result, CodeGenError> { + match self { + ModuleBlockElements::Block(block) => { + block.generate_register_interface(parent_name, parent_path, files) + } + ModuleBlockElements::Register(register) => { + register.generate_register_interface(parent_name, parent_path, files) + } + ModuleBlockElements::Memory(_memory) => todo!(), + ModuleBlockElements::Fifo(_fifo) => todo!(), + } + } +} + +impl CodeGen for Block { + fn generate_register_interface( + self, + parent_name: Option, + parent_path: Option, + mut files: HashMap, + ) -> Result, CodeGenError> { + if self.multiple.is_some() { + // Plan: expand automatically, or same as register? + return Err(CodeGenError::UnsupportedStructure { + name: "multiple in block", + }); + } + let parent_name = parent_name.ok_or(CodeGenError::ParentMissing { module: "Block" })?; + let parent_path = parent_path.ok_or(CodeGenError::ParentMissing { module: "Block" })?; + + 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 accessors_methods = self.elements.iter().map(|e| { + let child_name = e.get_name(); + let snake_case_name = util::parse_to_ident(&child_name.to_snake_case())?; + match e { + ModuleBlockElements::Block(_) => { + let child_upper_camel_name = util::parse_to_ident(&child_name.to_upper_camel_case())?; + Ok(quote! { + pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name { + #snake_case_name::#child_upper_camel_name::new(self.mem_ptr) + } + }) + }, + ModuleBlockElements::Register(register) => { + let child_upper_camel_name = util::parse_to_ident(&format!("Reg{}", child_name.to_upper_camel_case()))?; + match ®ister.multiple { + None => { + Ok(quote! { + pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name { + #snake_case_name::#child_upper_camel_name::new(self.mem_ptr) + } + }) + }, + Some(multiple_param) => { + let num_multiple = multiple_param.multiple as usize; + let elements = (0..num_multiple).map(|i| { + let offset = (multiple_param.offset as usize) * i; + quote! { + #snake_case_name::#child_upper_camel_name::new(unsafe { self.mem_ptr.add(#offset) } ) + } + }); + Ok(quote! { + pub fn #snake_case_name(&self) -> [#snake_case_name::#child_upper_camel_name; #num_multiple] { + [ #(#elements),* ] + } + }) + }, + } + }, + ModuleBlockElements::Memory(_memory) => todo!(), + ModuleBlockElements::Fifo(_fifo) => todo!(), + } + }).collect::, CodeGenError>>()?; + + let parent_struct = if parent_name == util::parse_to_ident("RegisterInterface").unwrap() { + quote! {#parent_name} + } else { + quote! {#parent_name<'a>} + }; + + let child_mods = self + .elements + .iter() + .map(|e| { + util::parse_to_ident(&e.get_name().to_snake_case()).map(|child_name| { + quote! { + pub mod #child_name; + } + }) + }) + .collect::, _>>()?; + + let out = quote! { + #![doc = #desc] + + use std::marker::PhantomData; + + use super::#parent_name; + + #(#child_mods)* + + const OFFSET: usize = #addr; + + pub struct #upper_camel_name<'a> { + mem_ptr: *mut u32, + _marker: PhantomData<&'a mut #parent_struct>, + } + + impl #upper_camel_name<'_> { + pub(crate) fn new(parent_ptr: *mut u32) -> Self { + #upper_camel_name { + mem_ptr: unsafe { parent_ptr.add(OFFSET) }, + _marker: PhantomData, + } + } + + #(#accessors_methods)* + } + }; + let (out_path, next_parent_path) = mod_file_path(parent_path, &snake_case_name); + log::info!("{:?}", out_path); + if let Some(old_out) = files.insert(out_path.clone(), out.clone()) { + log::error!("path {}", out_path.display()); + log::error!("old {}", old_out.to_string()); + log::error!("new {}", out.to_string()); + return Err(CodeGenError::FilePathError(snake_case_name.to_string())); + }; + + let files = self.elements.into_iter().try_fold(files, |files, e| { + e.generate_register_interface( + Some(upper_camel_name.clone()), + Some(next_parent_path.clone()), + files, + ) + })?; + + Ok(files) + } +} + +/// Get filepath to write the `mod_snake_case_name` and next parent_path. +fn mod_file_path( + parent_path: path::PathBuf, + mod_snake_case_name: &proc_macro2::Ident, +) -> (path::PathBuf, path::PathBuf) { + ( + parent_path.join(format!("{}.rs", mod_snake_case_name)), + parent_path.join(mod_snake_case_name.to_string()), + ) +} + +mod codegen_register; +mod codegen_registerspec_impl; + +/// Internally calls functions in [`codegen_register`] +impl CodeGen for Register { + fn generate_register_interface( + self, + parent_name: Option, + parent_path: Option, + mut files: HashMap, + ) -> Result, CodeGenError> { + let parent_name = parent_name.ok_or(CodeGenError::ParentMissing { module: "Block" })?; + let parent_path = parent_path.ok_or(CodeGenError::ParentMissing { module: "Block" })?; + + 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 = + codegen_registerspec_impl::gen_registerspec_impl( + reg_name.clone(), + self.modf, + type_t, + type_ux, + ); + + let desc = self.desc.unwrap_or("".to_string()); + + let out = quote! { + #![doc = #desc] + + 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::#parent_name<'a>>, + } + + impl #reg_name<'_> { + pub(crate) fn new(parent_ptr: *mut u32) -> Self { + #reg_name { + mem_ptr: unsafe { parent_ptr.add(OFFSET) }, + _marker: PhantomData, + } + } + } + + #code_reg_def + + #code_t_def + }; + + let (out_path, _next_parent_path) = mod_file_path(parent_path, &snake_case_name); + log::info!("{:?}", out_path); + if files.insert(out_path, out).is_some() { + return Err(CodeGenError::FilePathError(snake_case_name.to_string())); + } + Ok(files) + } +} diff --git a/src/generator/codegen_register.rs b/src/generator/codegen_register.rs new file mode 100644 index 0000000..4445086 --- /dev/null +++ b/src/generator/codegen_register.rs @@ -0,0 +1,661 @@ +//! Generator for [`Register`]. +//! The entry point is [`reg_type_def`]. + +use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use itertools::Itertools; +use proc_macro2::{Ident, Literal, TokenStream}; +use quote::quote; + +use crate::types::{DataType, Field, MultipleParams, Register, Value}; + +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 { + Ok(Self(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 #upper_camel_name { + #(#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<#upper_camel_name> for u32 { + fn from(value: #upper_camel_name) -> Self { + value.inner + } + } + }; + + Ok((out, upper_camel_name.clone(), type_ux)) +} + +enum FieldType<'a> { + RustType(RustUxTypes), + CustomValue(&'a [Value]), +} + +/// 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_snake_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 = match field.elements.is_empty() { + true => FieldType::RustType(util::RustUxTypes::from_mask(field.mask)), + false => FieldType::CustomValue(&field.elements), + }; + + 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, + &field.desc.clone().unwrap_or("".to_string()), + ), + None => generate_single_field( + mask_name, + base_type, + field.mask, + field_type, + snake_case_name, + &field.desc.clone().unwrap_or("".to_string()), + ), + }; + + Ok((code_mask, code_getter, code_setter)) +} + +/// Generate "single" field definition (mask, getter, setter). +/// +/// # Cases +/// This function is separated into three cases based on field types [`FieldType`]. +/// +/// - field is `bool` => [`generate_single_bool_field`] +/// - field is `u8`/`u16`/`u32` => [`generate_single_ux_field`] +/// - field is custom [`Value`] => [`generate_custom_values_const_enumdef`] & [`generate_single_custom_values_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: FieldType, + snake_case_name: Ident, + desc: &str, +) -> (TokenStream, TokenStream, TokenStream) { + let mask_val = util::parse_to_literal(&format!("0x{:x}", mask_val)).unwrap(); + let code_mask = match field_type { + FieldType::RustType(_) => { + quote! { + const #mask_name: #base_type = #mask_val; + } + } + FieldType::CustomValue(values) => { + let additional = + generate_custom_values_const_enumdef(&base_type, &snake_case_name, values); + quote! { + const #mask_name: #base_type = #mask_val; + #additional + } + } + }; + let (code_getter, code_setter) = match field_type { + FieldType::RustType(field_type) => match field_type { + RustUxTypes::Bool => generate_single_bool_field(mask_name, snake_case_name, desc), + RustUxTypes::U8 | RustUxTypes::U16 | RustUxTypes::U32 => { + generate_single_ux_field(mask_name, base_type, snake_case_name, field_type, desc) + } + }, + FieldType::CustomValue(values) => { + generate_single_custom_values_field(mask_name, snake_case_name, values, desc) + } + }; + + (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, + desc: &str, +) -> (TokenStream, TokenStream) { + let setter_name = util::parse_to_ident(&format!("set_{}", snake_case_name)).unwrap(); + let getter_doc = format!("Getter. {}", desc); + let code_getter = quote! { + #[doc = #getter_doc] + pub fn #snake_case_name(&self) -> bool { + (self.inner & #mask_name) == #mask_name + } + }; + let setter_doc = format!("Setter. {}", desc); + let code_setter = quote! { + #[doc = #setter_doc] + 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, + desc: &str, +) -> (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 getter_doc = format!("Getter. {}", desc); + let code_getter = quote! { + #[doc = #getter_doc] + 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 setter_doc = format!("Setter. {}", desc); + let code_setter = quote! { + #[doc = #setter_doc] + pub fn #setter_name(&self, val: #field_type) -> Self { + let update: #base_type = (val as #base_type) & #mask_name; + let mut inner = self.inner; + inner &= !#mask_name; + inner |= update; + Self { inner } + } + }; + (code_getter, code_setter) +} + +fn custom_value_const_name(field_name: &Ident, value_name: &str) -> Ident { + util::parse_to_ident(&format!( + "{}_{}", + field_name.to_string().to_shouty_snake_case(), + value_name.to_shouty_snake_case() + )) + .unwrap() +} + +/// Generate const var and value enum definition. +/// +/// Both for single & multiple. +fn generate_custom_values_const_enumdef( + base_type: &Ident, + field_name: &Ident, + values: &[Value], +) -> TokenStream { + let consts = values.iter().map(|value| { + let const_name = custom_value_const_name(field_name, &value.name); + let val = value.data; + quote! { + const #const_name: #base_type = #val; + } + }); + let variants = values.iter().map(|value| { + let desc = value.desc.clone().unwrap_or("".to_string()); + let variant_name = util::parse_to_ident(&value.name.to_upper_camel_case()).unwrap(); + quote! { + #[doc = #desc] + #variant_name + } + }); + let value_enum_name = + util::parse_to_ident(&field_name.to_string().to_upper_camel_case()).unwrap(); + + quote! { + #(#consts)* + pub enum #value_enum_name { + #(#variants),* + } + } +} + +/// Generate custom [`Value`] "single" field definition (getter, setter). +fn generate_single_custom_values_field( + mask_name: Ident, + snake_case_name: Ident, + values: &[Value], + desc: &str, +) -> (TokenStream, TokenStream) { + let setter_name = util::parse_to_ident(&format!("set_{}", snake_case_name)).unwrap(); + let value_enum_name = + util::parse_to_ident(&snake_case_name.to_string().to_upper_camel_case()).unwrap(); + let (getter_match_arms, setter_match_arms): (Vec<_>, Vec<_>) = values + .iter() + .map(|value| { + let const_name = custom_value_const_name(&snake_case_name, &value.name); + let variant_name = util::parse_to_ident(&value.name.to_upper_camel_case()).unwrap(); + ( + quote! { + #const_name => #value_enum_name::#variant_name + }, + quote! { + #value_enum_name::#variant_name => #const_name + }, + ) + }) + .unzip(); + + let getter_doc = format!("Getter. {}", desc); + let code_getter = quote! { + #[doc = #getter_doc] + pub fn #snake_case_name(&self) -> #value_enum_name { + match (self.inner & #mask_name) + >> #mask_name.trailing_zeros() + { + #(#getter_match_arms),*, + _ => panic!("must not reachable"), + } + } + }; + + let setter_doc = format!("Setter. {}", desc); + let code_setter = quote! { + #[doc = #setter_doc] + pub fn #setter_name(self, val: #value_enum_name) -> Self { + let val = match val { + #(#setter_match_arms),* + }; + let mut inner = self.inner; + inner &= !#mask_name; + inner |= val; + + 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` => [`generate_multiple_ux_field`] +/// - fields are custom [`Value`] => [`generate_custom_values_const_enumdef`] & [`generate_multiple_custom_values_field`] +fn generate_multiple_field( + mask_name: Ident, + base_type: Ident, + single_mask_val: u32, + single_field_type: FieldType, + snake_case_name: Ident, + multiple_params: &MultipleParams, + desc: &str, +) -> (TokenStream, TokenStream, TokenStream) { + let num_multiple = multiple_params.multiple; + let id_num_multiple = util::parse_to_literal(&num_multiple.to_string()).unwrap(); + let masks: Vec<_> = (0..multiple_params.multiple) + .map(|x| x * multiple_params.offset) + .map(|offset| single_mask_val << offset) + .map(|mask| util::parse_to_literal(&format!("0x{mask:x}")).unwrap()) + .collect(); + debug_assert_eq!(masks.len(), num_multiple.try_into().unwrap()); + let value_const_enumdefs = match single_field_type { + FieldType::RustType(_) => quote! {}, + FieldType::CustomValue(values) => { + generate_custom_values_const_enumdef(&base_type, &snake_case_name, values) + } + }; + let code_mask = quote! { + const #mask_name: [#base_type; #id_num_multiple] = [#(#masks),*]; + #value_const_enumdefs + }; + + let (code_getter, code_setter) = match single_field_type { + FieldType::RustType(single_field_type) => match single_field_type { + RustUxTypes::Bool => generate_multiple_bool_field( + mask_name, + base_type, + snake_case_name, + masks.clone(), + desc, + ), + RustUxTypes::U8 | RustUxTypes::U16 | RustUxTypes::U32 => generate_multiple_ux_field( + mask_name, + base_type, + snake_case_name, + single_field_type, + masks, + desc, + ), + }, + FieldType::CustomValue(values) => generate_multiple_custom_values_field( + mask_name, + base_type, + snake_case_name, + masks, + values, + desc, + ), + }; + + (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, + desc: &str, +) -> (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 getter_doc = format!("Getter. {}", desc); + let code_getter = quote! { + #[doc = #getter_doc] + 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 setter_doc = format!("Setter. {}", desc); + let code_setter = quote! { + #[doc = #setter_doc] + pub fn #setter_name(self, val: [bool; #num_multiple]) -> Self { + let mask: #base_type = #mask_name.iter().sum(); + let update: #base_type = #mask_name + .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, + desc: &str, +) -> (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 getter_doc = format!("Getter. {}", desc); + let code_getter = quote! { + #[doc = #getter_doc] + 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 setter_doc = format!("Setter. {}", desc); + let code_setter = quote! { + #[doc = #setter_doc] + 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) +} + +/// Generate custom [`Value`] "single" field definition (getter, setter). +fn generate_multiple_custom_values_field( + mask_name: Ident, + base_type: Ident, + snake_case_name: Ident, + masks: Vec, + values: &[Value], + desc: &str, +) -> (TokenStream, TokenStream) { + let value_enum_name = + util::parse_to_ident(&snake_case_name.to_string().to_upper_camel_case()).unwrap(); + let num_multiple = masks.len(); + let (getter_match_arms, setter_match_arms): (Vec<_>, Vec<_>) = values + .iter() + .map(|value| { + let const_name = custom_value_const_name(&snake_case_name, &value.name); + let variant_name = util::parse_to_ident(&value.name.to_upper_camel_case()).unwrap(); + ( + quote! { + #const_name => #value_enum_name::#variant_name + }, + quote! { + #value_enum_name::#variant_name => #const_name + }, + ) + }) + .unzip(); + + let elem_getter = masks.iter().enumerate().map(|(i, _mask)| { + quote! { + match ((self.inner & #mask_name[#i])) + >> (#mask_name[#i].trailing_zeros()) + { + #(#getter_match_arms),*, + _ => panic!("must not reachable"), + } + } + }); + let getter_doc = format!("Getter. {}", desc); + let code_getter = quote! { + #[doc = #getter_doc] + pub fn #snake_case_name(&self) -> [#value_enum_name; #num_multiple] { + [ + #(#elem_getter),* + ] + } + }; + + let setter_name = util::parse_to_ident(&format!("set_{}", snake_case_name)).unwrap(); + let elem_setter = masks.iter().enumerate().map(|(i, _mask)| { + quote! { + match val[#i] { + #(#setter_match_arms),* + } + } + }); + let setter_doc = format!("Setter. {}", desc); + let code_setter = quote! { + #[doc = #setter_doc] + pub fn #setter_name(&self, val: [#value_enum_name; #num_multiple]) -> Self { + let val: [#base_type; #num_multiple] = [ + #(#elem_setter),* + ]; + 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/generator/codegen_registerspec_impl.rs b/src/generator/codegen_registerspec_impl.rs new file mode 100644 index 0000000..a1b0edc --- /dev/null +++ b/src/generator/codegen_registerspec_impl.rs @@ -0,0 +1,36 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +use crate::types::RwSpecifier; + +pub(super) fn gen_registerspec_impl( + reg_name: Ident, + modf: RwSpecifier, + type_t: Ident, + type_ux: Ident, +) -> TokenStream { + let impl_rw = match modf { + RwSpecifier::R => quote! { + impl Readable for #reg_name<'_> {} + }, + RwSpecifier::W => quote! { + impl Writable for #reg_name<'_> {} + }, + RwSpecifier::RW => quote! { + impl Readable for #reg_name<'_> {} + impl Writable for #reg_name<'_> {} + impl Modifiable for #reg_name<'_> {} + }, + }; + quote! { + impl RegisterSpec for #reg_name<'_> { + type Ux = #type_ux; + type T = #type_t; + + fn as_ptr(&self) -> *mut Self::Ux { + self.mem_ptr + } + } + #impl_rw + } +} diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..2f345da --- /dev/null +++ b/src/io.rs @@ -0,0 +1,27 @@ +//! File IO for generated codes. + +use std::{collections::HashMap, fs, io, path}; + +/// Write formatted codes generated with [`Module::generate_code`](crate::types::Module::generate_code). +pub fn write_to_files( + files: HashMap, + out_path: &path::Path, +) -> io::Result<()> { + if !out_path.is_dir() { + return Err(io::Error::from(io::ErrorKind::NotADirectory)); + } + if fs::read_dir(out_path)?.next().is_some() { + return Err(io::Error::new( + io::ErrorKind::AlreadyExists, + "out path is not empty", + )); + } + for (file_path, code) in files { + fs::DirBuilder::new() + .recursive(true) + .create(out_path.join(&file_path).parent().unwrap())?; + + fs::write(out_path.join(&file_path), prettyplease::unparse(&code))?; + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 477c59e..4129c60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,46 @@ -//! Root document [`types::Module::from_xml_dom`] +//! Generate register interface software from register map in XML. +//! +//! # Example +//! +//! Here's a typical usage: +//! ```no_run +//! use endcap_sl_software_ri_generator::types; +//! +//! let xmlfile = std::fs::read_to_string("./csr.xml")?; +//! let doc = roxmltree::Document::parse_with_options( +//! &xmlfile, +//! roxmltree::ParsingOptions { +//! allow_dtd: true, +//! nodes_limit: u32::MAX, +//! }, +//! )?; +//! +//! let register_map = types::Module::from_xml_dom(doc.root_element())?; +//! +//! let files = register_map.generate_code()?; +//! endcap_sl_software_ri_generator::write_to_files(files, &std::path::PathBuf::from("out"))?; +//! +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! # 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`]. +//! +//! # modules +//! - [`types`]: type definitions of internal register map representation +//! - [`converter`]: DOM to internal representation +//! - [`generator`]: internal representation to rust code +//! - [`io`]: formatting and printing -pub mod types; -pub mod parser; pub mod converter; +pub mod generator; +pub mod io; +mod parser; +mod type_traits; +pub mod types; + +pub use io::write_to_files; diff --git a/src/main.rs b/src/main.rs index b042c79..a3a63bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,12 @@ use std::fs; use anyhow::Result; use endcap_sl_software_ri_generator::types; +use itertools::Itertools; fn main() -> Result<()> { env_logger::init(); + log::debug!("logger enabled"); - println!("Hello, world!"); let xmlfile = fs::read_to_string("./csr.xml")?; let doc = roxmltree::Document::parse_with_options( &xmlfile, @@ -15,11 +16,26 @@ fn main() -> Result<()> { nodes_limit: u32::MAX, }, )?; - // println!("Parsed: {:#?}", doc); - // println!("Root: {:?}", doc.root_element()); + log::debug!("Parsed: {:#?}", doc); let register_map = types::Module::from_xml_dom(doc.root_element())?; - println!("read: {:#?}", register_map); + log::info!("read: {:?}", register_map); + log::debug!("read: {:#?}", register_map); + + let files = register_map.generate_code()?; + if log::log_enabled!(log::Level::Debug) { + for (path, code) in &files { + log::debug!("path: {:?}", path); + log::debug!("{}", prettyplease::unparse(code)); + } + } + if log::log_enabled!(log::Level::Info) { + for filepath in files.keys().sorted() { + log::info!("{}", filepath.display()); + } + } + + endcap_sl_software_ri_generator::write_to_files(files, &std::path::PathBuf::from("out"))?; Ok(()) } diff --git a/src/parser.rs b/src/parser.rs index 41882ef..87e86f9 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/type_traits.rs b/src/type_traits.rs new file mode 100644 index 0000000..d68da02 --- /dev/null +++ b/src/type_traits.rs @@ -0,0 +1,30 @@ +//! Util traits to get info from types in [`crate::types`]. + +use crate::types::{Block, ModuleBlockElements, Register}; + +pub(crate) trait GetName { + fn get_name(&self) -> String; +} + +impl GetName for ModuleBlockElements { + fn get_name(&self) -> String { + match self { + ModuleBlockElements::Block(block) => block.get_name(), + ModuleBlockElements::Register(register) => register.get_name(), + ModuleBlockElements::Memory(_memory) => todo!(), + ModuleBlockElements::Fifo(_fifo) => todo!(), + } + } +} + +impl GetName for Block { + fn get_name(&self) -> String { + self.name.clone() + } +} + +impl GetName for Register { + fn get_name(&self) -> String { + self.name.clone() + } +} diff --git a/src/types.rs b/src/types.rs index fa4ba30..65cc783 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,5 @@ +//! Register type definition. + #[derive(Debug)] pub struct Module { pub name: String, @@ -51,6 +53,8 @@ pub struct Register { pub name: String, /// Fill this with proper calc. pub addr: u32, + /// Fill this with proper calc. + pub r#type: DataType, pub mask: Option, /// Fill this with proper calc. pub modf: RwSpecifier, @@ -109,7 +113,7 @@ pub struct Field { pub struct Value { pub name: String, pub data: u32, - pub desc: Option + pub desc: Option, } #[derive(Debug, PartialEq)] @@ -122,7 +126,7 @@ pub enum AmodValues { USER2, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum DataType { D32, }