Skip to content

Commit 2fd1d1f

Browse files
authored
Merge pull request #767 from rust-embedded/isdefault
support of default enum value
2 parents d79beda + e17488c commit 2fd1d1f

File tree

5 files changed

+149
-61
lines changed

5 files changed

+149
-61
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
1414
- `FieldWriter` takes offset as struct field instead of const generic.
1515
Improves SVD field array access
1616
Add `width`, `offset` methods
17-
- *breaking change* Always numerates field arrays from 0
17+
- *breaking change* Always numerates field arrays from 0
18+
- Support of default value for `EnumeratedValues`
1819

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

Cargo.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ version = "0.14.3"
6464

6565
[dependencies.svd-rs]
6666
features = ["serde"]
67-
version = "0.14.4"
67+
version = "0.14.5"
6868

6969
[dependencies.syn]
7070
version = "2.0"

src/generate/peripheral.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ pub fn render(p_original: &Peripheral, index: &Index, config: &Config) -> Result
8181
let name = &pi.name;
8282
let description = pi.description.as_deref().unwrap_or(&p.name);
8383
let name_str = name.to_sanitized_constant_case();
84-
let name_constant_case = Ident::new(&name, span);
84+
let name_constant_case = Ident::new(name, span);
8585
let address = util::hex(pi.base_address);
8686
let p_snake = name.to_sanitized_snake_case();
8787
snake_names.push(p_snake.to_string());

src/generate/register.rs

Lines changed: 137 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use crate::svd::{
2-
self, Access, BitRange, DimElement, EnumeratedValues, Field, MaybeArray, ModifiedWriteValues,
3-
ReadAction, Register, RegisterProperties, Usage, WriteConstraint,
2+
self, Access, BitRange, DimElement, EnumeratedValue, EnumeratedValues, Field, MaybeArray,
3+
ModifiedWriteValues, ReadAction, Register, RegisterProperties, Usage, WriteConstraint,
44
};
55
use core::u64;
66
use log::warn;
77
use proc_macro2::{Ident, Punct, Spacing, Span, TokenStream};
88
use quote::{quote, ToTokens};
9-
use std::borrow::Cow;
109
use std::collections::HashSet;
1110
use std::fmt::Write;
11+
use std::{borrow::Cow, collections::BTreeMap};
1212
use svd_parser::expand::{
1313
derive_enumerated_values, derive_field, BlockPath, EnumPath, FieldPath, Index, RegisterPath,
1414
};
@@ -472,7 +472,7 @@ fn render_register_mod_debug(
472472
log::debug!("register={} field={}", name, f.name);
473473
if field_access.can_read() && f.read_action.is_none() {
474474
if let Field::Array(_, de) = &f {
475-
for (_, suffix) in de.indexes().enumerate() {
475+
for suffix in de.indexes() {
476476
let f_name_n = util::replace_suffix(&f.name, &suffix)
477477
.to_snake_case_ident(Span::call_site());
478478
let f_name_n_s = format!("{f_name_n}");
@@ -730,11 +730,24 @@ pub fn fields(
730730
// later on is the same as the read enumeration, we reuse and do not generate again.
731731
evs_r = Some(evs);
732732

733-
// do we have finite definition of this enumeration in svd? If not, the later code would
734-
// return an Option when the value read from field does not match any defined values.
735-
let has_reserved_variant = evs.values.len() != (1 << width);
736733
// parse enum variants from enumeratedValues svd record
737-
let variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
734+
let mut variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
735+
736+
let map = enums_to_map(evs);
737+
let mut def = evs
738+
.default_value()
739+
.and_then(|def| {
740+
minimal_hole(&map, width)
741+
.map(|v| Variant::from_value(v, def, config.pascal_enum_values))
742+
})
743+
.transpose()?;
744+
if variants.len() == 1 << width {
745+
def = None;
746+
} else if variants.len() == (1 << width) - 1 {
747+
if let Some(def) = def.take() {
748+
variants.push(def);
749+
}
750+
}
738751

739752
// if there's no variant defined in enumeratedValues, generate enumeratedValues with new-type
740753
// wrapper struct, and generate From conversation only.
@@ -743,8 +756,32 @@ pub fn fields(
743756
// generate struct VALUE_READ_TY_A(fty) and From<fty> for VALUE_READ_TY_A.
744757
add_with_no_variants(mod_items, &value_read_ty, &fty, &description, rv);
745758
} else {
759+
// do we have finite definition of this enumeration in svd? If not, the later code would
760+
// return an Option when the value read from field does not match any defined values.
761+
let has_reserved_variant;
762+
746763
// generate enum VALUE_READ_TY_A { ... each variants ... } and and From<fty> for VALUE_READ_TY_A.
747-
add_from_variants(mod_items, &variants, &value_read_ty, &fty, &description, rv);
764+
if let Some(def) = def.as_ref() {
765+
add_from_variants(
766+
mod_items,
767+
variants.iter().chain(std::iter::once(def)),
768+
&value_read_ty,
769+
&fty,
770+
&description,
771+
rv,
772+
);
773+
has_reserved_variant = false;
774+
} else {
775+
add_from_variants(
776+
mod_items,
777+
variants.iter(),
778+
&value_read_ty,
779+
&fty,
780+
&description,
781+
rv,
782+
);
783+
has_reserved_variant = evs.values.len() != (1 << width);
784+
}
748785

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

780822
// prepare the `variant` function. This function would return field value in
781823
// Rust structure; if we have reserved variant we return by Option.
782-
if has_reserved_variant {
783-
enum_items.extend(quote! {
784-
#[doc = "Get enumerated values variant"]
785-
#inline
786-
pub const fn variant(&self) -> Option<#value_read_ty> {
787-
match self.bits {
788-
#arms
789-
}
790-
}
791-
});
824+
let ret_ty = if has_reserved_variant {
825+
quote!(Option<#value_read_ty>)
792826
} else {
793-
enum_items.extend(quote! {
827+
quote!(#value_read_ty)
828+
};
829+
enum_items.extend(quote! {
794830
#[doc = "Get enumerated values variant"]
795831
#inline
796-
pub const fn variant(&self) -> #value_read_ty {
832+
pub const fn variant(&self) -> #ret_ty {
797833
match self.bits {
798834
#arms
799835
}
800-
}});
801-
}
836+
}
837+
});
802838

803839
// for each variant defined, we generate an `is_variant` function.
804840
for v in &variants {
@@ -823,6 +859,28 @@ pub fn fields(
823859
}
824860
});
825861
}
862+
if let Some(v) = def.as_ref() {
863+
let pc = &v.pc;
864+
let sc = &v.nksc;
865+
866+
let is_variant = Ident::new(
867+
&if sc.to_string().starts_with('_') {
868+
format!("is{sc}")
869+
} else {
870+
format!("is_{sc}")
871+
},
872+
span,
873+
);
874+
875+
let doc = util::escape_special_chars(&util::respace(&v.doc));
876+
enum_items.extend(quote! {
877+
#[doc = #doc]
878+
#inline
879+
pub fn #is_variant(&self) -> bool {
880+
matches!(self.variant(), #value_read_ty::#pc)
881+
}
882+
});
883+
}
826884
}
827885
}
828886

@@ -876,7 +934,7 @@ pub fn fields(
876934
}
877935
});
878936

879-
for fi in svd::field::expand(&f, de) {
937+
for fi in svd::field::expand(f, de) {
880938
let sub_offset = fi.bit_offset() as u64;
881939
let value = if sub_offset != 0 {
882940
let sub_offset = &unsuffixed(sub_offset);
@@ -961,7 +1019,20 @@ pub fn fields(
9611019
// if we writes to enumeratedValues, generate its structure if it differs from read structure.
9621020
if let Some((evs, None)) = lookup_filter(&lookup_results, Usage::Write) {
9631021
// parse variants from enumeratedValues svd record
964-
let variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
1022+
let mut variants = Variant::from_enumerated_values(evs, config.pascal_enum_values)?;
1023+
let map = enums_to_map(evs);
1024+
let mut def = evs
1025+
.default_value()
1026+
.and_then(|def| {
1027+
minimal_hole(&map, width)
1028+
.map(|v| Variant::from_value(v, def, config.pascal_enum_values))
1029+
})
1030+
.transpose()?;
1031+
if variants.len() == 1 << width {
1032+
} else if let Some(def) = def.take() {
1033+
variants.push(def);
1034+
unsafety = false;
1035+
}
9651036

9661037
// if the write structure is finite, it can be safely written.
9671038
if variants.len() == 1 << width {
@@ -979,7 +1050,7 @@ pub fn fields(
9791050
} else {
9801051
add_from_variants(
9811052
mod_items,
982-
&variants,
1053+
variants.iter(),
9831054
&value_write_ty,
9841055
&fty,
9851056
&description,
@@ -1130,7 +1201,7 @@ pub fn fields(
11301201
}
11311202
});
11321203

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

12131284
impl Variant {
12141285
fn from_enumerated_values(evs: &EnumeratedValues, pc: bool) -> Result<Vec<Self>> {
1215-
let span = Span::call_site();
12161286
evs.values
12171287
.iter()
12181288
// filter out all reserved variants, as we should not
12191289
// generate code for them
1220-
.filter(|field| field.name.to_lowercase() != "reserved" && field.is_default.is_none())
1290+
.filter(|ev| ev.name.to_lowercase() != "reserved" && !ev.is_default())
12211291
.map(|ev| {
12221292
let value = ev
12231293
.value
1224-
.ok_or_else(|| anyhow!("EnumeratedValue {} has no `<value>` field", ev.name))?;
1225-
1226-
let nksc = ev.name.to_sanitized_not_keyword_snake_case();
1227-
let sc = util::sanitize_keyword(nksc.clone());
1228-
Ok(Variant {
1229-
doc: ev
1230-
.description
1231-
.clone()
1232-
.unwrap_or_else(|| format!("`{value:b}`")),
1233-
pc: if pc {
1234-
ev.name.to_pascal_case_ident(span)
1235-
} else {
1236-
ev.name.to_constant_case_ident(span)
1237-
},
1238-
nksc: Ident::new(&nksc, span),
1239-
sc: Ident::new(&sc, span),
1240-
value,
1241-
})
1294+
.ok_or_else(|| anyhow!("EnumeratedValue {} has no `<value>` entry", ev.name))?;
1295+
Self::from_value(value, ev, pc)
12421296
})
12431297
.collect::<Result<Vec<_>>>()
12441298
}
1299+
fn from_value(value: u64, ev: &EnumeratedValue, pc: bool) -> Result<Self> {
1300+
let span = Span::call_site();
1301+
let nksc = ev.name.to_sanitized_not_keyword_snake_case();
1302+
let sc = util::sanitize_keyword(nksc.clone());
1303+
Ok(Variant {
1304+
doc: ev
1305+
.description
1306+
.clone()
1307+
.unwrap_or_else(|| format!("`{value:b}`")),
1308+
pc: if pc {
1309+
ev.name.to_pascal_case_ident(span)
1310+
} else {
1311+
ev.name.to_constant_case_ident(span)
1312+
},
1313+
nksc: Ident::new(&nksc, span),
1314+
sc: Ident::new(&sc, span),
1315+
value,
1316+
})
1317+
}
12451318
}
12461319

12471320
fn add_with_no_variants(
@@ -1283,9 +1356,9 @@ fn add_with_no_variants(
12831356
}
12841357
}
12851358

1286-
fn add_from_variants(
1359+
fn add_from_variants<'a>(
12871360
mod_items: &mut TokenStream,
1288-
variants: &[Variant],
1361+
variants: impl Iterator<Item = &'a Variant>,
12891362
pc: &Ident,
12901363
fty: &Ident,
12911364
desc: &str,
@@ -1298,7 +1371,7 @@ fn add_from_variants(
12981371
};
12991372

13001373
let mut vars = TokenStream::new();
1301-
for v in variants.iter().map(|v| {
1374+
for v in variants.map(|v| {
13021375
let desc = util::escape_special_chars(&util::respace(&format!("{}: {}", v.value, v.doc)));
13031376
let pcv = &v.pc;
13041377
let pcval = &unsuffixed(v.value);
@@ -1400,3 +1473,17 @@ fn lookup_filter(
14001473
.find(|evsbase| evsbase.0.usage == Some(usage))
14011474
.or_else(|| evs.first())
14021475
}
1476+
1477+
fn enums_to_map(evs: &EnumeratedValues) -> BTreeMap<u64, &EnumeratedValue> {
1478+
let mut map = BTreeMap::new();
1479+
for ev in &evs.values {
1480+
if let Some(v) = ev.value {
1481+
map.insert(v, ev);
1482+
}
1483+
}
1484+
map
1485+
}
1486+
1487+
fn minimal_hole(map: &BTreeMap<u64, &EnumeratedValue>, width: u32) -> Option<u64> {
1488+
(0..(1u64 << width)).find(|&v| !map.contains_key(&v))
1489+
}

0 commit comments

Comments
 (0)