Skip to content

Commit

Permalink
Merge pull request #767 from rust-embedded/isdefault
Browse files Browse the repository at this point in the history
support of default enum value
  • Loading branch information
burrbull authored Nov 23, 2023
2 parents d79beda + e17488c commit 2fd1d1f
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 61 deletions.
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))
}

0 comments on commit 2fd1d1f

Please sign in to comment.