diff --git a/CHANGELOG-python.md b/CHANGELOG-python.md index daa7491a..fcc43161 100644 --- a/CHANGELOG-python.md +++ b/CHANGELOG-python.md @@ -8,6 +8,7 @@ This changelog tracks the Python `svdtools` project. See * Support `_include` in peripherals in `device.yaml` * `-1` for default enum value * Strip `alternateRegister` too +* Ignore rule if starts with "?~" and no matched instances ## [v0.1.26] 2023-03-28 diff --git a/CHANGELOG-rust.md b/CHANGELOG-rust.md index 0760b0ba..ab427140 100644 --- a/CHANGELOG-rust.md +++ b/CHANGELOG-rust.md @@ -10,6 +10,7 @@ This changelog tracks the Rust `svdtools` project. See * Add `--enum_derive` flag * Strip `alternateRegister` too * Add `modifiedWriteValues` and `readAction` field patch (#156) +* Ignore rule if starts with "?~" and no matched instances * Fix #144 * Flag to check for errors after patching diff --git a/README.md b/README.md index 223f5947..4f6a53c8 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,18 @@ _rebase: _strip_end: - "_POSTFIX_" - +# You can list glob-like rules separated by commas to cover more periperals or registers at time. +# If rule is optional (peripheral may be missing in some devices) add `?~` in the header. +# Don't abuse it. First test not optional rule. +"?~TIM[18],TIM20": + CR2: + # Fields also support collecting in arrays + _array: + OIS?: + description: Output Idle state (OC%s output) + # Optional rules are supported here too + "?~OIS?N": + description: Output Idle state (OC%sN output) ``` ### Name Matching diff --git a/src/patch/device.rs b/src/patch/device.rs index 2c6bd151..c06da2a1 100644 --- a/src/patch/device.rs +++ b/src/patch/device.rs @@ -9,7 +9,7 @@ use std::{fs::File, io::Read, path::Path}; use super::iterators::{MatchIter, Matched}; use super::peripheral::{PeripheralExt, RegisterBlockExt}; use super::yaml_ext::{AsType, GetVal}; -use super::{abspath, matchname, Config, PatchResult, VAL_LVL}; +use super::{abspath, matchname, Config, PatchResult, Spec, VAL_LVL}; use super::{make_address_block, make_address_blocks, make_cpu, make_interrupt, make_peripheral}; use super::{make_dim_element, modify_dim_element, modify_register_properties}; @@ -418,12 +418,13 @@ impl DeviceExt for Device { ) -> PatchResult { // Find all peripherals that match the spec let mut pcount = 0; + let (pspec, ignore) = pspec.spec(); for ptag in self.iter_peripherals(pspec) { pcount += 1; ptag.process(peripheral, config) .with_context(|| format!("Processing peripheral `{}`", ptag.name))?; } - if pcount == 0 { + if !ignore && pcount == 0 { Err(anyhow!( "Could not find `{pspec}. Present peripherals: {}.`", self.peripherals.iter().map(|p| p.name.as_str()).join(", ") diff --git a/src/patch/mod.rs b/src/patch/mod.rs index 7b9d96e2..ef4121b0 100644 --- a/src/patch/mod.rs +++ b/src/patch/mod.rs @@ -736,3 +736,18 @@ fn common_description(descs: &[Option<&str>], dim_index: &[String]) -> Option (&str, bool); +} + +impl Spec for str { + fn spec(&self) -> (&str, bool) { + if let Some(s) = self.strip_prefix("?~") { + (s, true) + } else { + (self, false) + } + } +} diff --git a/src/patch/peripheral.rs b/src/patch/peripheral.rs index bce7fa5c..479bf50b 100644 --- a/src/patch/peripheral.rs +++ b/src/patch/peripheral.rs @@ -11,7 +11,7 @@ use super::register::{RegisterExt, RegisterInfoExt}; use super::yaml_ext::{AsType, GetVal, ToYaml}; use super::{ check_offsets, common_description, make_dim_element, matchname, matchsubspec, - modify_dim_element, spec_ind, Config, PatchResult, VAL_LVL, + modify_dim_element, spec_ind, Config, PatchResult, Spec, VAL_LVL, }; use super::{make_cluster, make_interrupt, make_register}; @@ -407,6 +407,7 @@ impl RegisterBlockExt for Peripheral { } fn modify_register(&mut self, rspec: &str, rmod: &Hash) -> PatchResult { + // TODO: empty error let rtags = self.iter_registers(rspec).collect::>(); if !rtags.is_empty() { let register_builder = make_register(rmod)?; @@ -557,6 +558,7 @@ impl RegisterBlockExt for Peripheral { } fn modify_cluster(&mut self, cspec: &str, cmod: &Hash) -> PatchResult { + // TODO: empty error let ctags = self.iter_clusters(cspec).collect::>(); if !ctags.is_empty() { let cluster_builder = make_cluster(cmod)?; @@ -646,12 +648,13 @@ impl RegisterBlockExt for Peripheral { // Find all registers that match the spec let mut rcount = 0; let pname = self.name.clone(); + let (rspec, ignore) = rspec.spec(); for rtag in self.iter_registers(rspec) { rcount += 1; rtag.process(rmod, &pname, config) .with_context(|| format!("Processing register `{}`", rtag.name))?; } - if rcount == 0 { + if !ignore && rcount == 0 { Err(anyhow!( "Could not find `{pname}:{rspec}. Present registers: {}.`", self.registers().map(|r| r.name.as_str()).join(", ") @@ -665,12 +668,13 @@ impl RegisterBlockExt for Peripheral { // Find all clusters that match the spec let mut ccount = 0; let pname = self.name.clone(); + let (cspec, ignore) = cspec.spec(); for ctag in self.iter_clusters(cspec) { ccount += 1; ctag.process(cmod, &pname, config) .with_context(|| format!("Processing cluster `{}`", ctag.name))?; } - if ccount == 0 { + if !ignore && ccount == 0 { Err(anyhow!( "Could not find `{pname}:{cspec}. Present clusters: {}.`", self.clusters().map(|c| c.name.as_str()).join(", ") @@ -1112,12 +1116,13 @@ impl RegisterBlockExt for Cluster { // Find all registers that match the spec let mut rcount = 0; let pname = self.name.clone(); + let (rspec, ignore) = rspec.spec(); for rtag in self.iter_registers(rspec) { rcount += 1; rtag.process(rmod, &pname, config) .with_context(|| format!("Processing register `{}`", rtag.name))?; } - if rcount == 0 { + if !ignore && rcount == 0 { Err(anyhow!( "Could not find `{pname}:{}:{rspec}. Present registers: {}.`", self.name, @@ -1132,12 +1137,13 @@ impl RegisterBlockExt for Cluster { // Find all clusters that match the spec let mut ccount = 0; let pname = self.name.clone(); + let (cspec, ignore) = cspec.spec(); for ctag in self.iter_clusters(cspec) { ccount += 1; ctag.process(cmod, &pname, config) .with_context(|| format!("Processing cluster `{}`", ctag.name))?; } - if ccount == 0 { + if !ignore && ccount == 0 { Err(anyhow!( "Could not find `{pname}:{}:{cspec}. Present clusters: {}.`", self.name, @@ -1159,6 +1165,7 @@ fn collect_in_array( let mut registers = Vec::new(); let mut place = usize::MAX; let mut i = 0; + let (rspec, ignore) = rspec.spec(); while i < regs.len() { match ®s[i] { RegisterCluster::Register(Register::Single(r)) if matchname(&r.name, rspec) => { @@ -1171,6 +1178,9 @@ fn collect_in_array( } } if registers.is_empty() { + if ignore { + return Ok(()); + } return Err(anyhow!( "{path}: registers {rspec} not found. Present registers: {}.`", regs.iter() @@ -1299,9 +1309,9 @@ fn collect_in_cluster( if rspec == "description" { continue; } - rspecs.push(rspec.to_string()); let mut registers = Vec::new(); let mut i = 0; + let (rspec, ignore) = rspec.spec(); while i < regs.len() { match ®s[i] { RegisterCluster::Register(Register::Single(r)) if matchname(&r.name, rspec) => { @@ -1314,6 +1324,9 @@ fn collect_in_cluster( } } if registers.is_empty() { + if ignore { + continue; + } return Err(anyhow!( "{path}: registers {rspec} not found. Present registers: {}.`", regs.iter() @@ -1324,6 +1337,7 @@ fn collect_in_cluster( .join(", ") )); } + rspecs.push(rspec.to_string()); if single { if registers.len() > 1 { return Err(anyhow!("{path}: more than one registers {rspec} found")); @@ -1384,6 +1398,11 @@ fn collect_in_cluster( } rdict.insert(rspec.to_string(), registers); } + if rdict.is_empty() { + return Err(anyhow!( + "{path}: registers cannot be collected into {cname} cluster. No matches found" + )); + } let address_offset = rdict .values() .min_by_key(|rs| rs[0].address_offset) diff --git a/src/patch/register.rs b/src/patch/register.rs index fc7985dd..74cf2cd3 100644 --- a/src/patch/register.rs +++ b/src/patch/register.rs @@ -12,7 +12,7 @@ use super::iterators::{MatchIter, Matched}; use super::yaml_ext::{AsType, GetVal, ToYaml}; use super::{ check_offsets, common_description, make_dim_element, matchname, modify_dim_element, spec_ind, - Config, PatchResult, VAL_LVL, + Config, PatchResult, Spec, VAL_LVL, }; use super::{make_derived_enumerated_values, make_ev_array, make_ev_name, make_field}; @@ -370,6 +370,7 @@ impl RegisterExt for Register { let mut fields = Vec::new(); let mut place = usize::MAX; let mut i = 0; + let (fspec, ignore) = fspec.spec(); while i < fs.len() { match &fs[i] { Field::Single(f) if matchname(&f.name, fspec) => { @@ -382,6 +383,9 @@ impl RegisterExt for Register { } } if fields.is_empty() { + if ignore { + return Ok(()); + } return Err(anyhow!( "{}: fields {fspec} not found. Present fields: {}.`", self.name, @@ -448,9 +452,13 @@ impl RegisterExt for Register { Ok(()) } fn split_fields(&mut self, fspec: &str, fsplit: &Hash) -> PatchResult { + let (fspec, ignore) = fspec.spec(); let mut it = self.iter_fields(fspec); let (new_fields, name) = match (it.next(), it.next()) { (None, _) => { + if ignore { + return Ok(()); + } return Err(anyhow!( "Could not find any fields to split {}:{fspec}. Present fields: {}.`", self.name, @@ -721,6 +729,7 @@ impl RegisterExt for Register { set_enum(ftag, evs.clone(), orig_usage, true, access)?; } } else { + let (fspec, ignore) = fspec.spec(); let mut offsets: Vec<_> = Vec::new(); for (i, f) in self.fields().enumerate() { if matchname(&f.name, fspec) { @@ -728,6 +737,9 @@ impl RegisterExt for Register { } } if offsets.is_empty() { + if ignore { + return Ok(()); + } return Err(anyhow!( "Could not find field {pname}:{}:{fspec}. Present fields: {}.`", self.name, @@ -779,6 +791,7 @@ impl RegisterExt for Register { fn process_field_range(&mut self, pname: &str, fspec: &str, fmod: &[Yaml]) -> PatchResult { let mut set_any = false; + let (fspec, ignore) = fspec.spec(); for ftag in self.iter_fields(fspec) { ftag.write_constraint = Some(WriteConstraint::Range(WriteConstraintRange { min: fmod[0].i64()? as u64, @@ -786,7 +799,7 @@ impl RegisterExt for Register { })); set_any = true; } - if !set_any { + if !ignore && !set_any { return Err(anyhow!( "Could not find field {pname}:{}:{fspec}. Present fields: {}.`", self.name, diff --git a/svdtools/patch.py b/svdtools/patch.py index dacbe8d6..e6d9e2f3 100644 --- a/svdtools/patch.py +++ b/svdtools/patch.py @@ -56,10 +56,19 @@ def dict_constructor(loader, node, deep=False): yaml.add_constructor(_mapping_tag, dict_constructor, yaml.SafeLoader) +def get_spec(spec): + if spec.startswith("?~"): + return spec[2:], True + else: + return spec, False + + def matchname(name, spec): """Check if name matches against a specification.""" if spec.startswith("_"): return False + if spec.startswith("?~"): + raise SvdPatchError(f"{spec}: optional rules are not supported here yet") if "{" in spec: return any(fnmatchcase(name, subspec) for subspec in braceexpand(spec)) else: @@ -649,6 +658,7 @@ def clear_fields(self, pspec): def process_peripheral(self, pspec, peripheral, update_fields=True): """Work through a peripheral, handling all registers.""" # Find all peripherals that match the spec + pspec, ignore = get_spec(pspec) pcount = 0 for ptag in self.iter_peripherals(pspec): pcount += 1 @@ -766,7 +776,7 @@ def process_peripheral(self, pspec, peripheral, update_fields=True): cluster = peripheral["_clusters"][cspec] p.process_cluster(cspec, cluster, update_fields) - if pcount == 0: + if not ignore and pcount == 0: raise MissingPeripheralError(f"Could not find {pspec}") @@ -1026,6 +1036,7 @@ def strip(self, substr, strip_end=False): def collect_in_array(self, rspec, rmod): """Collect same registers in peripheral into register array.""" registers = [] + rspec, ignore = get_spec(rspec) li, ri = spec_ind(rspec) for rtag in list(self.iter_registers(rspec)): rname = rtag.findtext("name") @@ -1038,6 +1049,8 @@ def collect_in_array(self, rspec, rmod): ) dim = len(registers) if dim == 0: + if ignore: + return raise SvdPatchError( "{}: registers {} not found".format(self.ptag.findtext("name"), rspec) ) @@ -1094,6 +1107,7 @@ def collect_in_cluster(self, cname, cmod): check = True rspecs = [r for r in cmod if r != "description"] for rspec in rspecs: + rspec, ignore = get_spec(rspec) registers = [] for rtag, match_rspec in list(self.iter_registers_with_matches(rspec)): rname = rtag.findtext("name") @@ -1105,14 +1119,17 @@ def collect_in_cluster(self, cname, cmod): int(rtag.findtext("addressOffset"), 0), ] ) + if len(registers) == 0: + if ignore: + continue + raise SvdPatchError( + "{}: registers {rspec} not found".format(self.ptag.findtext("name")) + ) registers = sorted(registers, key=lambda r: r[2]) rdict[rspec] = registers bitmasks = [Register(r[0]).get_bitmask() for r in registers] if first: dim = len(registers) - if dim == 0: - check = False - break dimIndex = ",".join([r[1] for r in registers]) offsets = [r[2] for r in registers] dimIncrement = 0 @@ -1134,6 +1151,12 @@ def collect_in_cluster(self, cname, cmod): check = False break first = False + if not rdict: + raise SvdPatchError( + "{}: registers cannot be collected into {} cluster. No matches found".format( + self.ptag.findtext("name"), cname + ) + ) if not check: raise SvdPatchError( "{}: registers cannot be collected into {} cluster".format( @@ -1183,6 +1206,7 @@ def clear_fields(self, rspec): def process_register(self, rspec, register, update_fields=True): """Work through a register, handling all fields.""" # Find all registers that match the spec + rspec, ignore = get_spec(rspec) pname = self.ptag.find("name").text rcount = 0 for rtag in self.iter_registers(rspec): @@ -1235,7 +1259,7 @@ def process_register(self, rspec, register, update_fields=True): for fspec in register.get("_array", {}): fmod = register["_array"][fspec] r.collect_fields_in_array(fspec, fmod) - if rcount == 0: + if not ignore and rcount == 0: raise MissingRegisterError(f"Could not find {pname}:{rspec}") def process_cluster_tag( @@ -1273,12 +1297,13 @@ def process_cluster( assert isinstance(cspec, str) assert isinstance(cluster, OrderedDict) assert isinstance(update_fields, bool) + cspec, ignore = get_spec(cspec) # Find all clusters that match the spec ccount = 0 for ctag in self.iter_clusters(cspec): self.process_cluster_tag(ctag, cluster, update_fields) ccount += 1 - if ccount == 0: + if not ignore and ccount == 0: raise MissingClusterError(f"Could not find {self.name}:{cspec}") @@ -1511,6 +1536,7 @@ def merge_fields(self, key, value): def collect_fields_in_array(self, fspec, fmod): """Collect same fields in peripheral into register array.""" fields = [] + fspec, ignore = get_spec(fspec) li, ri = spec_ind(fspec) for ftag in list(self.iter_fields(fspec)): fname = ftag.findtext("name") @@ -1519,6 +1545,8 @@ def collect_fields_in_array(self, fspec, fmod): ) dim = len(fields) if dim == 0: + if ignore: + return raise SvdPatchError( "{}: fields {} not found".format(self.rtag.findtext("name"), fspec) ) @@ -1569,8 +1597,11 @@ def split_fields(self, fspec, fsplit): Split all fspec in rtag. Name and description can be customized with %s as a placeholder to the iterator value. """ + fspec, ignore = get_spec(fspec) fields = list(self.iter_fields(fspec)) if len(fields) == 0: + if ignore: + return rname = self.rtag.find("name").text raise RegisterMergeError( f"Could not find any fields to split {rname}.{fspec}" @@ -1676,6 +1707,8 @@ def process_field_enum(self, pname, fspec, field, usage="read-write"): field = field["_replace_enum"] replace_if_exists = True + fspec, ignore = get_spec(fspec) + derived, enum, enum_name, enum_usage = None, None, None, None for ftag in sorted_fields(list(self.iter_fields(fspec))): if "_derivedFrom" in field: @@ -1729,17 +1762,18 @@ def process_field_enum(self, pname, fspec, field, usage="read-write"): derived = enum_name else: ftag.append(make_derived_enumerated_values(derived)) - if derived is None: + if not ignore and derived is None: rname = self.rtag.find("name").text raise MissingFieldError(f"Could not find {pname}:{rname}.{fspec}") def process_field_range(self, pname, fspec, field): """Add a writeConstraint range given by field to all fspec in rtag.""" + fspec, ignore = get_spec(fspec) set_any = False for ftag in self.iter_fields(fspec): ftag.append(make_write_constraint(field)) set_any = True - if not set_any: + if not ignore and not set_any: rname = self.rtag.find("name").text raise MissingFieldError(f"Could not find {pname}:{rname}.{fspec}")