endcap-sl-software-ri-gener.../src/generator.rs

509 lines
18 KiB
Rust

//! 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::{
meta::{
GENERATOR_BUILD_TIMESTAMP, GENERATOR_GIT_COMMIT_TIMESTAMP, GENERATOR_GIT_DESCRIBE,
GENERATOR_GIT_SHA,
},
type_traits::GetName,
types::{Block, Module, ModuleBlockElements, Register},
};
use chrono::Local;
use heck::{ToSnakeCase, ToUpperCamelCase};
use itertools::Itertools;
use quote::quote;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CodeGenError {
#[error("tokenization(syn) error: {source}: {code}")]
SynError {
#[source]
source: syn::Error,
code: String,
},
#[error("failed to create file (name duplicated): {0}")]
FilePathDuplicatedError(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<proc_macro2::Ident, CodeGenError> {
syn::parse_str(s).map_err(|e| CodeGenError::SynError {
source: e,
code: s.to_string(),
})
}
pub(super) fn parse_to_literal(s: &str) -> Result<proc_macro2::Literal, CodeGenError> {
syn::parse_str(s).map_err(|e| CodeGenError::SynError {
source: e,
code: s.to_string(),
})
}
// 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"),
}
}
/// Derive appropriate rust type for right aligned `mask`ed value.
pub(super) fn from_exact_mask(mask: u32) -> Option<RustUxTypes> {
if mask.trailing_zeros() != 0 {
return None;
}
match 32 - mask.leading_zeros() {
0 => panic!("mask cannot be 0"),
1 => Some(RustUxTypes::Bool),
8 => Some(RustUxTypes::U8),
16 => Some(RustUxTypes::U16),
32 => Some(RustUxTypes::U32),
_ => None,
}
}
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<HashMap<path::PathBuf, syn::File>, CodeGenError> {
let build_metadata = format!(
"
This code is auto generated using endcap_sl_software_ri_generator.
# Build metadata
- timestamp: {}
## CSR XML
- sha256: {}
- git describe: {}
- git commit timestamp: {}
- git SHA: {}
## Generator
- build timestamp: {}
- git describe: {}
- git commit timestamp: {}
- git SHA: {}
",
Local::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, false),
hex::encode(self.xmlhash),
self.git_info.describe,
self.git_info.commit_timestamp,
self.git_info.sha,
GENERATOR_BUILD_TIMESTAMP,
GENERATOR_GIT_DESCRIBE,
GENERATOR_GIT_COMMIT_TIMESTAMP,
GENERATOR_GIT_SHA,
);
let desc = match self.desc {
Some(ref desc) => quote! { #![doc = #desc] },
None => quote! {},
};
let files = self.generate_register_interface(None, None, HashMap::new())?;
files
.into_iter()
.map(|(path, tokens)| -> Result<(PathBuf, syn::File), _> {
let tokens = if path.file_name().is_some_and(|file| file == "mod.rs") {
quote! {
#![doc = #build_metadata]
#desc
#tokens
}
} else {
tokens
};
let file: syn::File =
syn::parse2(tokens.clone()).map_err(|e| CodeGenError::SynError {
source: e,
code: tokens.to_string(),
})?;
Ok((path, file))
})
.process_results(|kv| HashMap::from_iter(kv))
}
}
trait CodeGen {
/// `parent_name` in UpperCamelCase.
fn generate_register_interface(
self,
parent_name: Option<proc_macro2::Ident>,
parent_path: Option<path::PathBuf>,
files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
) -> Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, CodeGenError>;
}
impl CodeGen for Module {
fn generate_register_interface(
self,
_: Option<proc_macro2::Ident>,
_: Option<path::PathBuf>,
mut files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
) -> std::result::Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, 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::<Result<Vec<_>, _>>()?;
let out = quote! {
#(#child_mods)*
pub use super::RegisterInterface;
};
files.insert(PathBuf::from("./mod.rs"), out);
let ident_register_interface = util::parse_to_ident("RegisterInterface").unwrap();
let register_interface_mod = PathBuf::from("./");
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<proc_macro2::Ident>,
parent_path: Option<path::PathBuf>,
files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
) -> Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, 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<proc_macro2::Ident>,
parent_path: Option<path::PathBuf>,
mut files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
) -> Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, 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<M> {
#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 &register.multiple {
None => {
Ok(quote! {
pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name<M> {
#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<M>; #num_multiple] {
[ #(#elements),* ]
}
})
},
}
},
ModuleBlockElements::Memory(_memory) => todo!(),
ModuleBlockElements::Fifo(_fifo) => todo!(),
}
}).collect::<Result<Vec<proc_macro2::TokenStream>, CodeGenError>>()?;
let parent_struct = if parent_name == util::parse_to_ident("RegisterInterface").unwrap() {
quote! {#parent_name<M>}
} else {
quote! {#parent_name<'a, M>}
};
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::<Result<Vec<_>, _>>()?;
let out = quote! {
#![doc = #desc]
use std::marker::PhantomData;
use mmapped_reg::mmapped::Mmapped;
use super::#parent_name;
#(#child_mods)*
const OFFSET: usize = #addr;
pub struct #upper_camel_name<'a, M: Mmapped<U = u32>> {
mem_ptr: *mut u32,
_marker: PhantomData<&'a mut #parent_struct>,
}
impl<M: Mmapped<U = u32>> #upper_camel_name<'_, M> {
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);
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::FilePathDuplicatedError(
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!("{mod_snake_case_name}.rs")),
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<proc_macro2::Ident>,
parent_path: Option<path::PathBuf>,
mut files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
) -> Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, 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 mmapped_reg::mmapped::Mmapped;
const OFFSET: usize = #addr;
pub struct #reg_name<'a, M: Mmapped<U = u32>> {
mem_ptr: *mut u32,
_marker: PhantomData<&'a mut super::#parent_name<'a, M>>,
}
impl<M: Mmapped<U = u32>> #reg_name<'_, M> {
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::FilePathDuplicatedError(
snake_case_name.to_string(),
));
}
Ok(files)
}
}