mirror of
https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator.git
synced 2025-07-17 17:09:27 +09:00
Merge branch 'generate-code' into 'main'
[Feat] implement code generation See merge request wotsubo/endcap-sl-software-ri-generator!3
This commit is contained in:
commit
b0660212fc
12 changed files with 1363 additions and 18 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -73,14 +73,26 @@ version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endcap-sl-software-ri-generator"
|
name = "endcap-sl-software-ri-generator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"heck",
|
||||||
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
|
"syn",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -107,6 +119,12 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
@ -119,6 +137,15 @@ version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.25"
|
version = "0.4.25"
|
||||||
|
@ -137,6 +164,16 @@ version = "1.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.93"
|
||||||
|
|
|
@ -14,6 +14,12 @@ path = "src/lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.95"
|
anyhow = "1.0.95"
|
||||||
env_logger = "0.11.6"
|
env_logger = "0.11.6"
|
||||||
|
heck = "0.5"
|
||||||
|
itertools = "0.14"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
prettyplease = "0.2"
|
||||||
|
proc-macro2 = "1.0.93"
|
||||||
|
quote = "1.0"
|
||||||
roxmltree = "0.20"
|
roxmltree = "0.20"
|
||||||
|
syn = "2.0.96"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
|
|
|
@ -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};
|
use std::{num, str};
|
||||||
|
|
||||||
|
@ -11,6 +15,7 @@ use crate::types::{
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Possible errors in conversion, with positional information.
|
||||||
#[derive(Debug, Error, PartialEq)]
|
#[derive(Debug, Error, PartialEq)]
|
||||||
pub enum DomConversionError {
|
pub enum DomConversionError {
|
||||||
#[error("attribute {attr} not found in element: {start} - {end}", start = pos.0, end = pos.1)]
|
#[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,
|
param: &'static str,
|
||||||
pos: (TextPos, TextPos),
|
pos: (TextPos, TextPos),
|
||||||
},
|
},
|
||||||
#[error("other dom conversion error: {0}")]
|
#[error("other dom conversion error: {comment}: {start} - {end}", start = pos.0, end = pos.1)]
|
||||||
OtherError(String),
|
OtherError {
|
||||||
|
comment: &'static str,
|
||||||
|
pos: (TextPos, TextPos),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DomConversionError {
|
impl DomConversionError {
|
||||||
|
@ -110,6 +118,15 @@ impl DomConversionError {
|
||||||
found,
|
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 {
|
mod util {
|
||||||
|
@ -121,7 +138,11 @@ mod util {
|
||||||
|
|
||||||
pub(crate) fn get_name(node: Node) -> Result<String, DomConversionError> {
|
pub(crate) fn get_name(node: Node) -> Result<String, DomConversionError> {
|
||||||
match node.attribute("name") {
|
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)),
|
None => Err(DomConversionError::attr_not_found("name", node)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,8 +302,9 @@ impl Block {
|
||||||
.next()
|
.next()
|
||||||
{
|
{
|
||||||
Some(s) => Ok(s.to_string()),
|
Some(s) => Ok(s.to_string()),
|
||||||
None => Err(DomConversionError::OtherError(
|
None => Err(DomConversionError::other_error(
|
||||||
"decoder format is not yet fixed".to_string(),
|
"decoder format is not yet fixed",
|
||||||
|
node,
|
||||||
)),
|
)),
|
||||||
}?,
|
}?,
|
||||||
};
|
};
|
||||||
|
@ -323,6 +345,18 @@ impl Register {
|
||||||
.map_err(|e| DomConversionError::parse_prefixed_u32_error(e, "addr", node))?,
|
.map_err(|e| DomConversionError::parse_prefixed_u32_error(e, "addr", node))?,
|
||||||
None => 0,
|
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
|
let mask = node
|
||||||
.attribute("mask")
|
.attribute("mask")
|
||||||
.map(|addr| {
|
.map(|addr| {
|
||||||
|
@ -352,15 +386,41 @@ impl Register {
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let desc = node.attribute("desc").map(str::to_string);
|
let desc = node.attribute("desc").map(str::to_string);
|
||||||
|
|
||||||
let children = node
|
let children: Vec<_> = node
|
||||||
.children()
|
.children()
|
||||||
.filter(|node| node.is_element() && node.tag_name().name().eq("field"))
|
.filter(|node| node.is_element() && node.tag_name().name().eq("field"))
|
||||||
.map(Field::from_xml_dom)
|
.map(Field::from_xml_dom)
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
// 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 {
|
Ok(Register {
|
||||||
name,
|
name,
|
||||||
addr,
|
addr,
|
||||||
|
r#type,
|
||||||
mask,
|
mask,
|
||||||
modf,
|
modf,
|
||||||
multiple,
|
multiple,
|
||||||
|
@ -372,13 +432,13 @@ impl Register {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Memory {
|
impl Memory {
|
||||||
pub(crate) fn from_xml_dom(node: Node) -> Result<Self, DomConversionError> {
|
pub(crate) fn from_xml_dom(_node: Node) -> Result<Self, DomConversionError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fifo {
|
impl Fifo {
|
||||||
pub(crate) fn from_xml_dom(node: Node) -> Result<Self, DomConversionError> {
|
pub(crate) fn from_xml_dom(_node: Node) -> Result<Self, DomConversionError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -424,6 +484,7 @@ impl Field {
|
||||||
.map(Value::from_xml_dom)
|
.map(Value::from_xml_dom)
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
// Validation
|
||||||
if let Some(default) = default {
|
if let Some(default) = default {
|
||||||
if default & !(mask) != 0 {
|
if default & !(mask) != 0 {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
|
|
424
src/generator.rs
Normal file
424
src/generator.rs
Normal file
|
@ -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<proc_macro2::Ident, CodeGenError> {
|
||||||
|
Ok(syn::parse_str(s)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn parse_to_literal(s: &str) -> Result<proc_macro2::Literal, CodeGenError> {
|
||||||
|
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<HashMap<path::PathBuf, syn::File>, 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<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)*
|
||||||
|
};
|
||||||
|
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<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 {
|
||||||
|
#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::<Result<Vec<proc_macro2::TokenStream>, 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::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
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<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 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)
|
||||||
|
}
|
||||||
|
}
|
661
src/generator/codegen_register.rs
Normal file
661
src/generator/codegen_register.rs
Normal file
|
@ -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<Ux> for T` and `impl From<T> 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<Self, Self::Error> {
|
||||||
|
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<TokenStream>,
|
||||||
|
Vec<TokenStream>,
|
||||||
|
Vec<TokenStream>,
|
||||||
|
) = 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<Self, Self::Error> {
|
||||||
|
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<Literal>,
|
||||||
|
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<Literal>,
|
||||||
|
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<Literal>,
|
||||||
|
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)
|
||||||
|
}
|
36
src/generator/codegen_registerspec_impl.rs
Normal file
36
src/generator/codegen_registerspec_impl.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
27
src/io.rs
Normal file
27
src/io.rs
Normal file
|
@ -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<path::PathBuf, syn::File>,
|
||||||
|
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(())
|
||||||
|
}
|
47
src/lib.rs
47
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 converter;
|
||||||
|
pub mod generator;
|
||||||
|
pub mod io;
|
||||||
|
mod parser;
|
||||||
|
mod type_traits;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub use io::write_to_files;
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -2,11 +2,12 @@ use std::fs;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use endcap_sl_software_ri_generator::types;
|
use endcap_sl_software_ri_generator::types;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
log::debug!("logger enabled");
|
||||||
|
|
||||||
println!("Hello, world!");
|
|
||||||
let xmlfile = fs::read_to_string("./csr.xml")?;
|
let xmlfile = fs::read_to_string("./csr.xml")?;
|
||||||
let doc = roxmltree::Document::parse_with_options(
|
let doc = roxmltree::Document::parse_with_options(
|
||||||
&xmlfile,
|
&xmlfile,
|
||||||
|
@ -15,11 +16,26 @@ fn main() -> Result<()> {
|
||||||
nodes_limit: u32::MAX,
|
nodes_limit: u32::MAX,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
// println!("Parsed: {:#?}", doc);
|
log::debug!("Parsed: {:#?}", doc);
|
||||||
// println!("Root: {:?}", doc.root_element());
|
|
||||||
|
|
||||||
let register_map = types::Module::from_xml_dom(doc.root_element())?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
//! Module for converting XML custom token string to rust types.
|
//! Module for converting XML custom token string to rust types.
|
||||||
|
//!
|
||||||
|
//! Unlike, [`crate::converter`], this only has string "parser".
|
||||||
|
|
||||||
use std::any;
|
use std::any;
|
||||||
|
|
||||||
|
|
30
src/type_traits.rs
Normal file
30
src/type_traits.rs
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Register type definition.
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -51,6 +53,8 @@ pub struct Register {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Fill this with proper calc.
|
/// Fill this with proper calc.
|
||||||
pub addr: u32,
|
pub addr: u32,
|
||||||
|
/// Fill this with proper calc.
|
||||||
|
pub r#type: DataType,
|
||||||
pub mask: Option<u32>,
|
pub mask: Option<u32>,
|
||||||
/// Fill this with proper calc.
|
/// Fill this with proper calc.
|
||||||
pub modf: RwSpecifier,
|
pub modf: RwSpecifier,
|
||||||
|
@ -109,7 +113,7 @@ pub struct Field {
|
||||||
pub struct Value {
|
pub struct Value {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub data: u32,
|
pub data: u32,
|
||||||
pub desc: Option<String>
|
pub desc: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -122,7 +126,7 @@ pub enum AmodValues {
|
||||||
USER2,
|
USER2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum DataType {
|
pub enum DataType {
|
||||||
D32,
|
D32,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue