Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support of default enum value #767

Merged
merged 3 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- `FieldWriter` takes offset as struct field instead of const generic.
Improves SVD field array access
Add `width`, `offset` methods
- *breaking change* Always numerates field arrays from 0
- *breaking change* Always numerates field arrays from 0
- Support of default value for `EnumeratedValues`

## [v0.30.3] - 2023-11-19

Expand Down
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ version = "0.14.3"

[dependencies.svd-rs]
features = ["serde"]
version = "0.14.4"
version = "0.14.5"

[dependencies.syn]
version = "2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/generate/peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result
let name = &pi.name;
let description = pi.description.as_deref().unwrap_or(&p.name);
let name_str = name.to_sanitized_constant_case();
let name_constant_case = Ident::new(&name, span);
let name_constant_case = Ident::new(name, span);
let address = util::hex(pi.base_address);
let p_snake = name.to_sanitized_snake_case();
snake_names.push(p_snake.to_string());
Expand Down
187 changes: 137 additions & 50 deletions src/generate/register.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::svd::{
self, Access, BitRange, DimElement, EnumeratedValues, Field, MaybeArray, ModifiedWriteValues,
ReadAction, Register, RegisterProperties, Usage, WriteConstraint,
self, Access, BitRange, DimElement, EnumeratedValue, EnumeratedValues, Field, MaybeArray,
ModifiedWriteValues, ReadAction, Register, RegisterProperties, Usage, WriteConstraint,
};
use core::u64;
use log::warn;
use proc_macro2::{Ident, Punct, Spacing, Span, TokenStream};
use quote::{quote, ToTokens};
use std::borrow::Cow;
use std::collections::HashSet;
use std::fmt::Write;
use std::{borrow::Cow, collections::BTreeMap};
use svd_parser::expand::{
derive_enumerated_values, derive_field, BlockPath, EnumPath, FieldPath, Index, RegisterPath,
};
Expand Down Expand Up @@ -472,7 +472,7 @@ fn render_register_mod_debug(
log::debug!("register={} field={}", name, f.name);
if field_access.can_read() && f.read_action.is_none() {
if let Field::Array(_, de) = &f {
for (_, suffix) in de.indexes().enumerate() {
for suffix in de.indexes() {
let f_name_n = util::replace_suffix(&f.name, &suffix)
.to_snake_case_ident(Span::call_site());
let f_name_n_s = format!("{f_name_n}");
Expand Down Expand Up @@ -730,11 +730,24 @@ pub fn fields(
// later on is the same as the read enumeration, we reuse and do not generate again.
evs_r = Some(evs);

// do we have finite definition of this enumeration in svd? If not, the later code would
// return an Option when the value read from field does not match any defined values.
let has_reserved_variant = evs.values.len() != (1 << width);
// parse enum variants from enumeratedValues svd record
let variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
let mut variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;

let map = enums_to_map(evs);
let mut def = evs
.default_value()
.and_then(|def| {
minimal_hole(&map, width)
.map(|v| Variant::from_value(v, def, config.pascal_enum_values))
})
.transpose()?;
if variants.len() == 1 << width {
def = None;
} else if variants.len() == (1 << width) - 1 {
if let Some(def) = def.take() {
variants.push(def);
}
}

// if there's no variant defined in enumeratedValues, generate enumeratedValues with new-type
// wrapper struct, and generate From conversation only.
Expand All @@ -743,8 +756,32 @@ pub fn fields(
// generate struct VALUE_READ_TY_A(fty) and From<fty> for VALUE_READ_TY_A.
add_with_no_variants(mod_items, &value_read_ty, &fty, &description, rv);
} else {
// do we have finite definition of this enumeration in svd? If not, the later code would
// return an Option when the value read from field does not match any defined values.
let has_reserved_variant;

// generate enum VALUE_READ_TY_A { ... each variants ... } and and From<fty> for VALUE_READ_TY_A.
add_from_variants(mod_items, &variants, &value_read_ty, &fty, &description, rv);
if let Some(def) = def.as_ref() {
add_from_variants(
mod_items,
variants.iter().chain(std::iter::once(def)),
&value_read_ty,
&fty,
&description,
rv,
);
has_reserved_variant = false;
} else {
add_from_variants(
mod_items,
variants.iter(),
&value_read_ty,
&fty,
&description,
rv,
);
has_reserved_variant = evs.values.len() != (1 << width);
}

// prepare code for each match arm. If we have reserved variant, the match operation would
// return an Option, thus we wrap the return value with Some.
Expand All @@ -771,6 +808,11 @@ pub fn fields(
arms.extend(quote! {
_ => None,
});
} else if let Some(v) = def.as_ref() {
let pc = &v.pc;
arms.extend(quote! {
_ => #value_read_ty::#pc,
});
} else if 1 << width.to_ty_width()? != variants.len() {
arms.extend(quote! {
_ => unreachable!(),
Expand All @@ -779,26 +821,20 @@ pub fn fields(

// prepare the `variant` function. This function would return field value in
// Rust structure; if we have reserved variant we return by Option.
if has_reserved_variant {
enum_items.extend(quote! {
#[doc = "Get enumerated values variant"]
#inline
pub const fn variant(&self) -> Option<#value_read_ty> {
match self.bits {
#arms
}
}
});
let ret_ty = if has_reserved_variant {
quote!(Option<#value_read_ty>)
} else {
enum_items.extend(quote! {
quote!(#value_read_ty)
};
enum_items.extend(quote! {
#[doc = "Get enumerated values variant"]
#inline
pub const fn variant(&self) -> #value_read_ty {
pub const fn variant(&self) -> #ret_ty {
match self.bits {
#arms
}
}});
}
}
});

// for each variant defined, we generate an `is_variant` function.
for v in &variants {
Expand All @@ -823,6 +859,28 @@ pub fn fields(
}
});
}
if let Some(v) = def.as_ref() {
let pc = &v.pc;
let sc = &v.nksc;

let is_variant = Ident::new(
&if sc.to_string().starts_with('_') {
format!("is{sc}")
} else {
format!("is_{sc}")
},
span,
);

let doc = util::escape_special_chars(&util::respace(&v.doc));
enum_items.extend(quote! {
#[doc = #doc]
#inline
pub fn #is_variant(&self) -> bool {
matches!(self.variant(), #value_read_ty::#pc)
}
});
}
}
}

Expand Down Expand Up @@ -876,7 +934,7 @@ pub fn fields(
}
});

for fi in svd::field::expand(&f, de) {
for fi in svd::field::expand(f, de) {
let sub_offset = fi.bit_offset() as u64;
let value = if sub_offset != 0 {
let sub_offset = &unsuffixed(sub_offset);
Expand Down Expand Up @@ -961,7 +1019,20 @@ pub fn fields(
// if we writes to enumeratedValues, generate its structure if it differs from read structure.
if let Some((evs, None)) = lookup_filter(&lookup_results, Usage::Write) {
// parse variants from enumeratedValues svd record
let variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
let mut variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
let map = enums_to_map(evs);
let mut def = evs
.default_value()
.and_then(|def| {
minimal_hole(&map, width)
.map(|v| Variant::from_value(v, def, config.pascal_enum_values))
})
.transpose()?;
if variants.len() == 1 << width {
} else if let Some(def) = def.take() {
variants.push(def);
unsafety = false;
}

// if the write structure is finite, it can be safely written.
if variants.len() == 1 << width {
Expand All @@ -979,7 +1050,7 @@ pub fn fields(
} else {
add_from_variants(
mod_items,
&variants,
variants.iter(),
&value_write_ty,
&fty,
&description,
Expand Down Expand Up @@ -1130,7 +1201,7 @@ pub fn fields(
}
});

for fi in svd::field::expand(&f, de) {
for fi in svd::field::expand(f, de) {
let sub_offset = fi.bit_offset() as u64;
let name_snake_case_n = &fi.name.to_snake_case_ident(Span::call_site());
let doc = description_with_bits(
Expand Down Expand Up @@ -1212,36 +1283,38 @@ struct Variant {

impl Variant {
fn from_enumerated_values(evs: &EnumeratedValues, pc: bool) -> Result<Vec<Self>> {
let span = Span::call_site();
evs.values
.iter()
// filter out all reserved variants, as we should not
// generate code for them
.filter(|field| field.name.to_lowercase() != "reserved" && field.is_default.is_none())
.filter(|ev| ev.name.to_lowercase() != "reserved" && !ev.is_default())
.map(|ev| {
let value = ev
.value
.ok_or_else(|| anyhow!("EnumeratedValue {} has no `<value>` field", ev.name))?;

let nksc = ev.name.to_sanitized_not_keyword_snake_case();
let sc = util::sanitize_keyword(nksc.clone());
Ok(Variant {
doc: ev
.description
.clone()
.unwrap_or_else(|| format!("`{value:b}`")),
pc: if pc {
ev.name.to_pascal_case_ident(span)
} else {
ev.name.to_constant_case_ident(span)
},
nksc: Ident::new(&nksc, span),
sc: Ident::new(&sc, span),
value,
})
.ok_or_else(|| anyhow!("EnumeratedValue {} has no `<value>` entry", ev.name))?;
Self::from_value(value, ev, pc)
})
.collect::<Result<Vec<_>>>()
}
fn from_value(value: u64, ev: &EnumeratedValue, pc: bool) -> Result<Self> {
let span = Span::call_site();
let nksc = ev.name.to_sanitized_not_keyword_snake_case();
let sc = util::sanitize_keyword(nksc.clone());
Ok(Variant {
doc: ev
.description
.clone()
.unwrap_or_else(|| format!("`{value:b}`")),
pc: if pc {
ev.name.to_pascal_case_ident(span)
} else {
ev.name.to_constant_case_ident(span)
},
nksc: Ident::new(&nksc, span),
sc: Ident::new(&sc, span),
value,
})
}
}

fn add_with_no_variants(
Expand Down Expand Up @@ -1283,9 +1356,9 @@ fn add_with_no_variants(
}
}

fn add_from_variants(
fn add_from_variants<'a>(
mod_items: &mut TokenStream,
variants: &[Variant],
variants: impl Iterator<Item = &'a Variant>,
pc: &Ident,
fty: &Ident,
desc: &str,
Expand All @@ -1298,7 +1371,7 @@ fn add_from_variants(
};

let mut vars = TokenStream::new();
for v in variants.iter().map(|v| {
for v in variants.map(|v| {
let desc = util::escape_special_chars(&util::respace(&format!("{}: {}", v.value, v.doc)));
let pcv = &v.pc;
let pcval = &unsuffixed(v.value);
Expand Down Expand Up @@ -1400,3 +1473,17 @@ fn lookup_filter(
.find(|evsbase| evsbase.0.usage == Some(usage))
.or_else(|| evs.first())
}

fn enums_to_map(evs: &EnumeratedValues) -> BTreeMap<u64, &EnumeratedValue> {
let mut map = BTreeMap::new();
for ev in &evs.values {
if let Some(v) = ev.value {
map.insert(v, ev);
}
}
map
}

fn minimal_hole(map: &BTreeMap<u64, &EnumeratedValue>, width: u32) -> Option<u64> {
(0..(1u64 << width)).find(|&v| !map.contains_key(&v))
}