diff --git a/.rustfmt.toml b/.rustfmt.toml index bd7a3e01..4699f4de 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -2,6 +2,6 @@ array_layout = "Block" fn_args_layout = "Block" fn_call_style = "Block" generics_indent = "Block" -max_width = 80 +max_width = 120 where_style = "Rfc" write_mode = "overwrite" \ No newline at end of file diff --git a/examples/.keep b/examples/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/normalise.sh b/examples/normalise.sh new file mode 100755 index 00000000..ec7c83ad --- /dev/null +++ b/examples/normalise.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +#End to end tests require some normalisation of files to correct for varying formatting by vendors (because the output format is static). + +# Normalise hexadecimal case: +gsed -i 's|\(0x[0-9a-fA-F]*\)|\L\1|g' $1 + +# Normalise hexadecimal lengths +gsed -i 's|>0x[0]\{1,2\}\([0-9a-f]\{1,2\}\)<|>0x\1<|g' $1 + +# Swap ST resets to full register width +gsed -i 's|0x00|0x00000000|g' $1 + +# Swap register sizes from hex to dec +gsed -i 's|0x20|32|g' $1 + +# Remove displayName props because we don't care +gsed -i 's|\(\s*[a-zA-Z0-9]*[\r\n]*\)||g' $1 + +# Remove empty descriptions +gsed -i 's|||g' $1 \ No newline at end of file diff --git a/src/access.rs b/src/access.rs new file mode 100644 index 00000000..f4bd57ae --- /dev/null +++ b/src/access.rs @@ -0,0 +1,85 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use helpers::{ParseElem, EncodeElem}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Access { + ReadOnly, + ReadWrite, + ReadWriteOnce, + WriteOnce, + WriteOnly, +} + +impl ParseElem for Access { + fn parse(tree: &Element) -> Access { + let text = try_get_child!(tree.text.as_ref()); + + match &text[..] { + "read-only" => Access::ReadOnly, + "read-write" => Access::ReadWrite, + "read-writeOnce" => Access::ReadWriteOnce, + "write-only" => Access::WriteOnly, + "writeOnce" => Access::WriteOnce, + _ => panic!("unknown access variant: {}", text), + } + } +} + +impl EncodeElem for Access { + fn encode(&self) -> Element { + let text = match *self { + Access::ReadOnly => String::from("read-only"), + Access::ReadWrite => String::from("read-write"), + Access::ReadWriteOnce => String::from("read-writeOnce"), + Access::WriteOnly => String::from("write-only"), + Access::WriteOnce => String::from("writeOnce"), + }; + + Element { + name: String::from("access"), + attributes: HashMap::new(), + children: Vec::new(), + text: Some(text), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let types = vec![ + (Access::ReadOnly, String::from("read-only")), + ( + Access::ReadWrite, + String::from("read-write") + ), + ( + Access::ReadWriteOnce, + String::from("read-writeOnce") + ), + ( + Access::WriteOnly, + String::from("write-only") + ), + ( + Access::WriteOnce, + String::from("writeOnce") + ), + ]; + + for (a, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let access = Access::parse(tree1); + assert_eq!(access, a, "Parsing `{}` expected `{:?}`", s, a); + let tree2 = &access.encode(); + assert_eq!(tree1, tree2, "Encoding {:?} expected {}", a, s); + } + } +} diff --git a/src/addressblock.rs b/src/addressblock.rs new file mode 100644 index 00000000..46ce46f6 --- /dev/null +++ b/src/addressblock.rs @@ -0,0 +1,73 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, new_element}; +use parse; + +#[derive(Clone, Debug, PartialEq)] +pub struct AddressBlock { + pub offset: u32, + pub size: u32, + pub usage: String, +} + +impl ParseElem for AddressBlock { + fn parse(tree: &Element) -> AddressBlock { + AddressBlock { + offset: try_get_child!(parse::u32(try_get_child!(tree.get_child("offset")))), + size: try_get_child!(parse::u32(try_get_child!(tree.get_child("size")))), + usage: try_get_child!(tree.get_child_text("usage")), + } + } +} + +impl EncodeElem for AddressBlock { + fn encode(&self) -> Element { + Element { + name: String::from("addressBlock"), + attributes: HashMap::new(), + children: vec![ + new_element("offset", Some(format!("{}", self.offset))), + new_element("size", Some(format!("0x{:08.x}", self.size))), + new_element("usage", Some(self.usage.clone())), + ], + text: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let types = vec![ + ( + AddressBlock { + offset: 0, + size: 0x00000800, + usage: String::from("registers"), + }, + String::from( + " + 0 + 0x00000800 + registers + ", + ) + ), + ]; + + for (a, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let v = AddressBlock::parse(tree1); + assert_eq!(v, a, "Parsing `{}` expected `{:?}`", s, a); + let tree2 = &v.encode(); + assert_eq!(tree1, tree2, "Encoding {:?} expected {}", a, s); + } + } +} diff --git a/src/bitrange.rs b/src/bitrange.rs new file mode 100644 index 00000000..f0a0fb4e --- /dev/null +++ b/src/bitrange.rs @@ -0,0 +1,146 @@ + +use xmltree::Element; + +use helpers::{ParseElem, EncodeChildren, new_element}; +use parse; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum BitRangeType { + BitRange, + OffsetWidth, + MsbLsb, +} + +// TODO: reimplement equality +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct BitRange { + pub offset: u32, + pub width: u32, + pub range_type: BitRangeType, +} + +impl ParseElem for BitRange { + fn parse(tree: &Element) -> BitRange { + let (end, start, range_type): (u32, u32, BitRangeType) = if let Some(range) = tree.get_child("bitRange") { + let text = try_get_child!(range.text.as_ref()); + + assert!(text.starts_with('[')); + assert!(text.ends_with(']')); + + let mut parts = text[1..text.len() - 1].split(':'); + + ( + try_get_child!(try_get_child!(parts.next()).parse()), + try_get_child!(try_get_child!(parts.next()).parse()), + BitRangeType::BitRange, + ) + } else if let (Some(lsb), Some(msb)) = (tree.get_child("lsb"), tree.get_child("msb")) { + ( + try_get_child!(parse::u32(msb)), + try_get_child!(parse::u32(lsb)), + BitRangeType::MsbLsb, + ) + } else { + return BitRange { + offset: try_get_child!(parse::u32(try_get_child!(tree.get_child("bitOffset")))), + width: try_get_child!(parse::u32(try_get_child!(tree.get_child("bitWidth")))), + range_type: BitRangeType::OffsetWidth, + }; + }; + + BitRange { + offset: start, + width: end - start + 1, + range_type: range_type, + } + } +} + + +impl EncodeChildren for BitRange { + fn encode_children(&self) -> Vec { + match self.range_type { + BitRangeType::BitRange => { + vec![ + new_element( + "bitRange", + Some(format!( + "[{}:{}]", + self.offset + self.width - 1, + self.offset + )) + ), + ] + } + BitRangeType::MsbLsb => { + vec![ + new_element("lsb", Some(format!("{}", self.offset))), + new_element("msb", Some(format!("{}", self.offset + self.width - 1))), + ] + } + BitRangeType::OffsetWidth => { + vec![ + new_element("bitOffset", Some(format!("{}", self.offset))), + new_element("bitWidth", Some(format!("{}", self.width))), + ] + } + } + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let types = vec![ + ( + BitRange { + offset: 16, + width: 4, + range_type: BitRangeType::BitRange, + }, + String::from( + " + [19:16] + ", + ) + ), + ( + BitRange { + offset: 16, + width: 4, + range_type: BitRangeType::OffsetWidth, + }, + String::from( + " + 164 + ", + ) + ), + ( + BitRange { + offset: 16, + width: 4, + range_type: BitRangeType::MsbLsb, + }, + String::from( + " + 1619 + ", + ) + ), + ]; + + for (a, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let value = BitRange::parse(tree1); + assert_eq!(value, a, "Parsing `{}` expected `{:?}`", s, a); + let mut tree2 = new_element("fake", None); + tree2.children = value.encode_children(); + assert_eq!(tree1, &tree2, "Encoding {:?} expected {}", a, s); + } + } +} diff --git a/src/cluster.rs b/src/cluster.rs new file mode 100644 index 00000000..74329217 --- /dev/null +++ b/src/cluster.rs @@ -0,0 +1,56 @@ + +use std::ops::Deref; +use std::collections::hash_map::*; + +use xmltree::Element; + +use helpers::{ParseElem, EncodeElem}; +use clusterinfo::ClusterInfo; +use registerclusterarrayinfo::RegisterClusterArrayInfo; + +#[derive(Clone, Debug, PartialEq)] +pub enum Cluster { + Single(ClusterInfo), + Array(ClusterInfo, RegisterClusterArrayInfo), +} + +impl ParseElem for Cluster { + fn parse(tree: &Element) -> Cluster { + assert_eq!(tree.name, "cluster"); + + let info = ClusterInfo::parse(tree); + + if tree.get_child("dimIncrement").is_some() { + let array_info = RegisterClusterArrayInfo::parse(tree); + assert!(info.name.contains("%s")); + if let Some(ref indices) = array_info.dim_index { + assert_eq!(array_info.dim as usize, indices.len()) + } + Cluster::Array(info, array_info) + } else { + Cluster::Single(info) + } + } +} + +impl EncodeElem for Cluster { + fn encode(&self) -> Element { + Element { + name: String::from("cluster"), + attributes: HashMap::new(), + children: Vec::new(), + text: None, + } + } +} + +impl Deref for Cluster { + type Target = ClusterInfo; + + fn deref(&self) -> &ClusterInfo { + match *self { + Cluster::Single(ref info) => info, + Cluster::Array(ref info, _) => info, + } + } +} diff --git a/src/clusterinfo.rs b/src/clusterinfo.rs new file mode 100644 index 00000000..09895106 --- /dev/null +++ b/src/clusterinfo.rs @@ -0,0 +1,65 @@ + +use xmltree::Element; +use either::Either; + + +use elementext::ElementExt; + +use helpers::{ParseElem, EncodeElem, new_element}; +use parse; + +use access::Access; +use register::Register; +use cluster::Cluster; +use registercluster::cluster_register_parse; + + +#[derive(Clone, Debug, PartialEq)] +pub struct ClusterInfo { + pub name: String, + pub description: String, + pub header_struct_name: String, + pub address_offset: u32, + pub size: Option, + pub access: Option, + pub reset_value: Option, + pub reset_mask: Option, + pub children: Vec>, + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl ParseElem for ClusterInfo { + fn parse(tree: &Element) -> ClusterInfo { + ClusterInfo { + name: try_get_child!(tree.get_child_text("name")), + description: try_get_child!(tree.get_child_text("description")), + header_struct_name: try_get_child!(tree.get_child_text("headerStructName")), + address_offset: { + try_get_child!(parse::u32(try_get_child!(tree.get_child("addressOffset")))) + }, + size: tree.get_child("size").map( + |t| try_get_child!(parse::u32(t)), + ), + access: tree.get_child("access").map(Access::parse), + reset_value: tree.get_child("resetValue").map(|t| { + try_get_child!(parse::u32(t)) + }), + reset_mask: tree.get_child("resetMask").map( + |t| try_get_child!(parse::u32(t)), + ), + children: tree.children + .iter() + .filter(|t| t.name == "register" || t.name == "cluster") + .map(cluster_register_parse) + .collect(), + _extensible: (), + } + } +} + +impl EncodeElem for ClusterInfo { + fn encode(&self) -> Element { + new_element("fake", None) + } +} diff --git a/src/cpu.rs b/src/cpu.rs new file mode 100644 index 00000000..afa04e66 --- /dev/null +++ b/src/cpu.rs @@ -0,0 +1,119 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use elementext::ElementExt; +use parse; + +use helpers::{ParseElem, EncodeElem, new_element}; +use endian::Endian; + +#[derive(Clone, Debug, PartialEq)] +pub struct Cpu { + pub name: String, + pub revision: String, + pub endian: Endian, + pub mpu_present: bool, + pub fpu_present: bool, + pub nvic_priority_bits: u32, + pub has_vendor_systick: bool, + + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl Cpu { + pub fn is_cortex_m(&self) -> bool { + self.name.starts_with("CM") + } +} + +impl ParseElem for Cpu { + fn parse(tree: &Element) -> Cpu { + // TODO: not sure where CPU comes from here? + // EFM32 SVDs appear to have "device" key with similar stuff under it. + assert_eq!(tree.name, "cpu"); + + Cpu { + name: try_get_child!(tree.get_child_text("name")), + revision: try_get_child!(tree.get_child_text("revision")), + endian: Endian::parse(try_get_child!(tree.get_child("endian"))), + mpu_present: try_get_child!(parse::bool(try_get_child!(tree.get_child("mpuPresent")))), + fpu_present: try_get_child!(parse::bool(try_get_child!(tree.get_child("fpuPresent")))), + nvic_priority_bits: try_get_child!(parse::u32(try_get_child!(tree.get_child("nvicPrioBits")))), + has_vendor_systick: try_get_child!(parse::bool( + try_get_child!(tree.get_child("vendorSystickConfig")), + )), + + _extensible: (), + } + } +} + +impl EncodeElem for Cpu { + fn encode(&self) -> Element { + Element { + name: String::from("cpu"), + attributes: HashMap::new(), + children: vec![ + new_element("name", Some(self.name.clone())), + new_element("revision", Some(self.revision.clone())), + self.endian.encode(), + new_element("mpuPresent", Some(format!("{}", self.mpu_present))), + new_element("fpuPresent", Some(format!("{}", self.fpu_present))), + new_element("nvicPrioBits", Some(format!("{}", self.nvic_priority_bits))), + new_element( + "vendorSystickConfig", + Some(format!("{}", self.has_vendor_systick)) + ), + ], + text: None, + } + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let types = vec![ + ( + Cpu { + name: String::from("EFM32JG12B500F512GM48"), + revision: String::from("5.1.1"), + endian: Endian::Little, + mpu_present: true, + fpu_present: true, + nvic_priority_bits: 8, + has_vendor_systick: false, + _extensible: (), + }, + String::from( + " + + EFM32JG12B500F512GM48 + 5.1.1 + little + true + true + 8 + false + + ", + ) + ), + ]; + + for (a, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let value = Cpu::parse(tree1); + assert_eq!(value, a, "Parsing `{}` expected `{:?}`", s, a); + let tree2 = value.encode(); + assert_eq!(tree1, &tree2, "Encoding {:?} expected {}", a, s); + } + } +} diff --git a/src/defaults.rs b/src/defaults.rs new file mode 100644 index 00000000..3cc512b8 --- /dev/null +++ b/src/defaults.rs @@ -0,0 +1,108 @@ + +use xmltree::Element; + +use helpers::{ParseElem, EncodeElem, EncodeChildren, new_element}; +use parse; + +use access::Access; + +/// Register default properties +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Defaults { + pub size: Option, + pub reset_value: Option, + pub reset_mask: Option, + pub access: Option, + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl ParseElem for Defaults { + fn parse(tree: &Element) -> Defaults { + Defaults { + size: tree.get_child("size").map( + |t| try_get_child!(parse::u32(t)), + ), + reset_value: tree.get_child("resetValue").map(|t| { + try_get_child!(parse::u32(t)) + }), + reset_mask: tree.get_child("resetMask").map( + |t| try_get_child!(parse::u32(t)), + ), + access: tree.get_child("access").map(Access::parse), + _extensible: (), + } + } +} + +impl EncodeChildren for Defaults { + fn encode_children(&self) -> Vec { + let mut children = Vec::new(); + + match self.size { + Some(ref v) => { + children.push(new_element("size", Some(format!("0x{:08.x}", v)))); + } + None => (), + }; + + match self.reset_value { + Some(ref v) => { + children.push(new_element("resetValue", Some(format!("0x{:08.x}", v)))); + } + None => (), + }; + + match self.reset_mask { + Some(ref v) => { + children.push(new_element("resetMask", Some(format!("0x{:08.x}", v)))); + } + None => (), + }; + + match self.access { + Some(ref v) => { + children.push(v.encode()); + } + None => (), + }; + + children + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let example = String::from( + " + + 0xaabbccdd + 0x11223344 + 0x00000000 + read-only + + ", + ); + + let expected = Defaults { + size: Some(0xaabbccdd), + reset_value: Some(0x11223344), + reset_mask: Some(0x00000000), + access: Some(Access::ReadOnly), + _extensible: (), + }; + + let tree1 = &try_get_child!(Element::parse(example.as_bytes())); + + let parsed = Defaults::parse(tree1); + assert_eq!(parsed, expected, "Parsing tree failed"); + + let mut tree2 = new_element("mock", None); + tree2.children = parsed.encode_children(); + assert_eq!(tree1, &tree2, "Encoding value failed"); + } +} diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 00000000..7d401a19 --- /dev/null +++ b/src/device.rs @@ -0,0 +1,100 @@ +use xmltree::Element; + +use std::collections::HashMap; + + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, new_element}; +use parse; + +use cpu::Cpu; +use defaults::Defaults; +use peripheral::Peripheral; + + + +#[derive(Clone, Debug)] +pub struct Device { + pub name: String, + schema_version: String, + pub version: String, + pub description: String, + pub address_unit_bits: u32, + pub width: u32, + pub cpu: Option, + pub peripherals: Vec, + pub defaults: Defaults, + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl ParseElem for Device { + fn parse(tree: &Element) -> Device { + Device { + schema_version: tree.attributes.get("schemaVersion").unwrap().clone(), + name: try_get_child!(tree.get_child_text("name")), + version: try_get_child!(tree.get_child_text("version")), + description: try_get_child!(tree.get_child_text("description")), + address_unit_bits: try_get_child!(parse::u32( + try_get_child!(tree.get_child("addressUnitBits")), + )), + width: try_get_child!(parse::u32(try_get_child!(tree.get_child("width")))), + cpu: tree.get_child("cpu").map(Cpu::parse), + peripherals: try_get_child!(tree.get_child("peripherals")) + .children + .iter() + .map(Peripheral::parse) + .collect(), + defaults: Defaults::parse(tree), + _extensible: (), + } + } +} + +impl EncodeElem for Device { + fn encode(&self) -> Element { + let mut elem = Element { + name: String::from("device"), + attributes: HashMap::new(), + children: vec![ + new_element("name", Some(self.name.clone())), + new_element("version", Some(self.version.clone())), + new_element("description", Some(self.description.clone())), + new_element( + "addressUnitBits", + Some(format!("{}", self.address_unit_bits)) + ), + new_element("width", Some(format!("{}", self.width))), + ], + text: None, + }; + + elem.attributes.insert( + String::from("xmlns:xs"), + String::from("http://www.w3.org/2001/XMLSchema-instance"), + ); + elem.attributes.insert( + String::from("schemaVersion"), + format!("{}", self.schema_version), + ); + elem.attributes.insert( + String::from("xs:noNamespaceSchemaLocation"), + format!("CMSIS-SVD_Schema_{}.xsd", self.schema_version), + ); + + match self.cpu { + Some(ref v) => { + elem.children.push(v.encode()); + } + None => (), + }; + elem.children.push(Element { + name: String::from("peripherals"), + attributes: HashMap::new(), + children: self.peripherals.iter().map(Peripheral::encode).collect(), + text: None, + }); + + elem + } +} diff --git a/src/elementext.rs b/src/elementext.rs new file mode 100644 index 00000000..7e49c306 --- /dev/null +++ b/src/elementext.rs @@ -0,0 +1,33 @@ + +use xmltree::Element; + +#[macro_export] +macro_rules! try_get_child { + ($e:expr) => { + $e.expect(concat!(file!(), ":", line!(), " ", stringify!($e))) + } +} + +pub trait ElementExt { + fn get_child_text(&self, k: K) -> Option + where + String: PartialEq; + fn debug(&self); +} + +impl ElementExt for Element { + fn get_child_text(&self, k: K) -> Option + where + String: PartialEq, + { + self.get_child(k).map(|c| try_get_child!(c.text.clone())) + } + + fn debug(&self) { + println!("<{}>", self.name); + for c in &self.children { + println!("{}: {:?}", c.name, c.text) + } + println!("", self.name); + } +} diff --git a/src/endian.rs b/src/endian.rs new file mode 100644 index 00000000..63ae2742 --- /dev/null +++ b/src/endian.rs @@ -0,0 +1,72 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use helpers::{ParseElem, EncodeElem}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Endian { + Little, + Big, + Selectable, + Other, +} + +impl ParseElem for Endian { + fn parse(tree: &Element) -> Endian { + let text = try_get_child!(tree.text.as_ref()); + + match &text[..] { + "little" => Endian::Little, + "big" => Endian::Big, + "selectable" => Endian::Selectable, + "other" => Endian::Other, + _ => panic!("unknown endian variant: {}", text), + } + } +} + +impl EncodeElem for Endian { + fn encode(&self) -> Element { + let text = match *self { + Endian::Little => String::from("little"), + Endian::Big => String::from("big"), + Endian::Selectable => String::from("selectable"), + Endian::Other => String::from("other"), + }; + + Element { + name: String::from("endian"), + attributes: HashMap::new(), + children: Vec::new(), + text: Some(text), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let types = vec![ + (Endian::Little, String::from("little")), + (Endian::Big, String::from("big")), + ( + Endian::Selectable, + String::from("selectable") + ), + (Endian::Other, String::from("other")), + ]; + + for (e, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let endian = Endian::parse(tree1); + assert_eq!(endian, e, "Parsing `{}` expected `{:?}`", s, e); + let tree2 = &endian.encode(); + assert_eq!(tree1, tree2, "Encoding {:?} expected {}", e, s); + } + } +} diff --git a/src/enumeratedvalue.rs b/src/enumeratedvalue.rs new file mode 100644 index 00000000..8897932b --- /dev/null +++ b/src/enumeratedvalue.rs @@ -0,0 +1,115 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use elementext::ElementExt; +use helpers::{EncodeElem, new_element}; +use parse; + + +#[derive(Clone, Debug, PartialEq)] +pub struct EnumeratedValue { + pub name: String, + pub description: Option, + pub value: Option, + pub is_default: Option, + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl EnumeratedValue { + pub fn parse(tree: &Element) -> Option { + assert_eq!(tree.name, "enumeratedValue"); + + Some(EnumeratedValue { + name: try_get_child!(tree.get_child_text("name")), + description: tree.get_child_text("description"), + value: tree.get_child("value").map( + |t| try_get_child!(parse::u32(t)), + ), + is_default: tree.get_child_text("isDefault").map(|t| { + try_get_child!(t.parse()) + }), + _extensible: (), + }) + } +} + +impl EncodeElem for EnumeratedValue { + fn encode(&self) -> Element { + let mut base = Element { + name: String::from("enumeratedValue"), + attributes: HashMap::new(), + children: vec![new_element("name", Some(self.name.clone()))], + text: None, + }; + + match self.description { + Some(ref d) => { + let s = (*d).clone(); + base.children.push(new_element("description", Some(s))); + } + None => (), + }; + + match self.value { + Some(ref v) => { + base.children.push(new_element( + "value", + Some(format!("0x{:08.x}", *v)), + )); + } + None => (), + }; + + match self.is_default { + Some(ref v) => { + base.children.push(new_element( + "isDefault", + Some(format!("{}", v)), + )); + } + None => (), + }; + + base + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let example = String::from( + " + + WS0 + Zero wait-states inserted in fetch or read transfers + 0x00000000 + true + + ", + ); + let expected = EnumeratedValue { + name: String::from("WS0"), + description: Some(String::from( + "Zero wait-states inserted in fetch or read transfers", + )), + value: Some(0), + is_default: Some(true), + _extensible: (), + }; + + let tree1 = &try_get_child!(Element::parse(example.as_bytes())); + + let parsed = EnumeratedValue::parse(tree1).unwrap(); + assert_eq!(parsed, expected, "Parsing tree failed"); + + let tree2 = &parsed.encode(); + assert_eq!(tree1, tree2, "Encoding value failed"); + } +} diff --git a/src/enumeratedvalues.rs b/src/enumeratedvalues.rs new file mode 100644 index 00000000..4d93a542 --- /dev/null +++ b/src/enumeratedvalues.rs @@ -0,0 +1,141 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, new_element}; + +use usage::Usage; +use enumeratedvalue::EnumeratedValue; + +#[derive(Clone, Debug, PartialEq)] +pub struct EnumeratedValues { + pub name: Option, + pub usage: Option, + pub derived_from: Option, + pub values: Vec, + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl ParseElem for EnumeratedValues { + fn parse(tree: &Element) -> EnumeratedValues { + assert_eq!(tree.name, "enumeratedValues"); + + EnumeratedValues { + name: tree.get_child_text("name"), + usage: tree.get_child("usage").map(Usage::parse), + derived_from: tree.attributes.get(&"derivedFrom".to_owned()).map(|s| { + s.to_owned() + }), + values: tree.children + .iter() + .filter_map(EnumeratedValue::parse) + .collect(), + _extensible: (), + } + } +} + +impl EncodeElem for EnumeratedValues { + fn encode(&self) -> Element { + let mut base = Element { + name: String::from("enumeratedValues"), + attributes: HashMap::new(), + children: Vec::new(), + text: None, + }; + + match self.name { + Some(ref d) => { + base.children.push(new_element("name", Some((*d).clone()))); + } + None => (), + }; + + match self.usage { + Some(ref v) => { + base.children.push(v.encode()); + } + None => (), + }; + + match self.derived_from { + Some(ref v) => { + base.attributes.insert( + String::from("derivedFrom"), + (*v).clone(), + ); + } + None => (), + } + + for v in &self.values { + base.children.push(v.encode()); + } + + base + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let example = String::from( + " + + + WS0 + Zero wait-states inserted in fetch or read transfers + 0x00000000 + true + + + WS1 + One wait-state inserted for each fetch or read transfer. See Flash Wait-States table for details + 0x00000001 + + + ", + ); + + let expected = EnumeratedValues { + name: None, + usage: None, + derived_from: Some(String::from("fake-derivation.png")), + values: vec![ + EnumeratedValue { + name: String::from("WS0"), + description: Some(String::from( + "Zero wait-states inserted in fetch or read transfers", + )), + value: Some(0), + is_default: Some(true), + _extensible: (), + }, + EnumeratedValue { + name: String::from("WS1"), + description: Some(String::from( + "One wait-state inserted for each fetch or read transfer. See Flash Wait-States table for details", + )), + value: Some(1), + is_default: None, + _extensible: (), + }, + ], + _extensible: (), + }; + + let tree1 = &try_get_child!(Element::parse(example.as_bytes())); + + let parsed = EnumeratedValues::parse(tree1); + assert_eq!(parsed, expected, "Parsing tree failed"); + + let tree2 = &parsed.encode(); + assert_eq!(tree1, tree2, "Encoding value failed"); + } +} diff --git a/src/field.rs b/src/field.rs new file mode 100644 index 00000000..d90b7cfe --- /dev/null +++ b/src/field.rs @@ -0,0 +1,155 @@ + +use std::collections::HashMap; +use xmltree::Element; + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, EncodeChildren, new_element}; + +use access::Access; +use writeconstraint::WriteConstraint; +use bitrange::BitRange; +use enumeratedvalues::EnumeratedValues; + + +#[derive(Clone, Debug, PartialEq)] +pub struct Field { + pub name: String, + pub description: Option, + pub bit_range: BitRange, + pub access: Option, + pub enumerated_values: Vec, + pub write_constraint: Option, + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl ParseElem for Field { + fn parse(tree: &Element) -> Field { + assert_eq!(tree.name, "field"); + + Field { + name: try_get_child!(tree.get_child_text("name")), + description: tree.get_child_text("description"), + bit_range: BitRange::parse(tree), + access: tree.get_child("access").map(Access::parse), + enumerated_values: tree.children + .iter() + .filter(|t| t.name == "enumeratedValues") + .map(EnumeratedValues::parse) + .collect::>(), + write_constraint: tree.get_child("writeConstraint").map( + WriteConstraint::parse, + ), + _extensible: (), + } + } +} + +impl EncodeElem for Field { + fn encode(&self) -> Element { + let mut elem = Element { + name: String::from("field"), + attributes: HashMap::new(), + children: vec![ + new_element("name", Some(self.name.clone())), + new_element("description", self.description.clone()), + ], + text: None, + }; + + // Add bit range + elem.children.append(&mut self.bit_range.encode_children()); + + match self.access { + Some(ref v) => { + elem.children.push(v.encode()); + } + None => (), + }; + + let mut enumerated_values: Vec = self.enumerated_values.iter().map(|v| v.encode()).collect(); + elem.children.append(&mut enumerated_values); + + match self.write_constraint { + Some(ref v) => { + elem.children.push(v.encode()); + } + None => (), + }; + + elem + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use bitrange::BitRangeType; + use enumeratedvalue::EnumeratedValue; + + #[test] + fn decode_encode() { + let types = vec![ + ( + Field { + name: String::from("MODE"), + description: Some(String::from("Read Mode")), + bit_range: BitRange { + offset: 24, + width: 2, + range_type: BitRangeType::OffsetWidth, + }, + access: Some(Access::ReadWrite), + enumerated_values: vec![ + EnumeratedValues { + name: None, + usage: None, + derived_from: None, + values: vec![ + EnumeratedValue { + name: String::from("WS0"), + description: Some(String::from( + "Zero wait-states inserted in fetch or read transfers", + )), + value: Some(0), + is_default: None, + _extensible: (), + }, + ], + _extensible: (), + }, + ], + write_constraint: None, + _extensible: (), + }, + String::from( + " + + MODE + Read Mode + 24 + 2 + read-write + + + WS0 + Zero wait-states inserted in fetch or read transfers + 0x00000000 + + + + ", + ) + ), + ]; + + for (a, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let v = Field::parse(tree1); + assert_eq!(v, a, "Parsing `{}` expected `{:?}`", s, a); + let tree2 = &v.encode(); + assert_eq!(tree1, tree2, "Encoding {:?} expected {}", a, s); + } + } +} diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 00000000..774ddb68 --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,34 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +// ParseElem parses an object from an XML element +pub trait ParseElem { + fn parse(tree: &Element) -> Self; +} + +// ParseChildren parses an object from children of an XML element +pub trait ParseChildren { + fn parse_children(tree: &Element) -> Self; +} + +// EncodeElem trait encodes an object to an XML element +pub trait EncodeElem { + fn encode(&self) -> Element; +} + +// EncodeChildren trait encodes an object to a vector of child elements +pub trait EncodeChildren { + fn encode_children(&self) -> Vec; +} + +// Helper to assist with creation of simple elements +pub fn new_element(name: &str, text: Option) -> Element { + Element { + name: String::from(name), + attributes: HashMap::new(), + children: Vec::new(), + text: text, + } +} diff --git a/src/interrupt.rs b/src/interrupt.rs new file mode 100644 index 00000000..e2e38290 --- /dev/null +++ b/src/interrupt.rs @@ -0,0 +1,77 @@ + +use std::collections::HashMap; + +use xmltree::Element; + + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, new_element}; +use parse; + + + +#[derive(Clone, Debug, PartialEq)] +pub struct Interrupt { + pub name: String, + pub description: Option, + pub value: u32, +} + +impl ParseElem for Interrupt { + fn parse(tree: &Element) -> Interrupt { + Interrupt { + name: try_get_child!(tree.get_child_text("name")), + description: tree.get_child_text("description"), + value: try_get_child!(parse::u32(try_get_child!(tree.get_child("value")))), + } + } +} + +impl EncodeElem for Interrupt { + fn encode(&self) -> Element { + Element { + name: String::from("interrupt"), + attributes: HashMap::new(), + children: vec![ + new_element("name", Some(self.name.clone())), + new_element("description", self.description.clone()), + new_element("value", Some(format!("{}", self.value))), + ], + text: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let types = vec![ + ( + Interrupt { + name: String::from("test"), + description: Some(String::from("description")), + value: 14, + }, + String::from( + " + + test + description + 14 + ", + ) + ), + ]; + + for (a, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let v = Interrupt::parse(tree1); + assert_eq!(v, a, "Parsing `{}` expected `{:?}`", s, a); + let tree2 = &v.encode(); + assert_eq!(tree1, tree2, "Encoding {:?} expected {}", a, s); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2461b5d2..9d4e954d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,642 +22,164 @@ //! - [SVD file database](https://github.com/posborne/cmsis-svd/tree/master/data) //! - [Sample SVD file](https://www.keil.com/pack/doc/CMSIS/SVD/html/svd_Example_pg.html) -#![deny(warnings)] +//#![deny(warnings)] extern crate either; extern crate xmltree; - -use std::ops::Deref; - -use either::Either; use xmltree::Element; -macro_rules! try { - ($e:expr) => { - $e.expect(concat!(file!(), ":", line!(), " ", stringify!($e))) - } -} - +#[macro_use] +mod elementext; mod parse; -/// Parses the contents of a SVD file (XML) -pub fn parse(xml: &str) -> Device { - Device::parse(xml) -} +mod helpers; +use helpers::*; +mod endian; +pub use endian::Endian; +mod access; +pub use access::Access; +mod usage; +pub use usage::Usage; +mod enumeratedvalue; +pub use enumeratedvalue::EnumeratedValue; +mod enumeratedvalues; +pub use enumeratedvalues::EnumeratedValues; +mod defaults; +pub use defaults::Defaults; +mod writeconstraintrange; +pub use writeconstraintrange::WriteConstraintRange; +mod writeconstraint; +pub use writeconstraint::WriteConstraint; +mod bitrange; +pub use bitrange::BitRange; +mod interrupt; +pub use interrupt::Interrupt; +mod addressblock; +pub use addressblock::AddressBlock; +mod field; +pub use field::Field; +mod register; +pub use register::Register; +mod clusterinfo; +pub use clusterinfo::ClusterInfo; +mod cluster; +pub use cluster::Cluster; +mod registerinfo; +pub use registerinfo::RegisterInfo; +mod registerarrayinfo; +pub use registerarrayinfo::RegisterArrayInfo; +mod registerclusterarrayinfo; +pub use registerclusterarrayinfo::RegisterClusterArrayInfo; +mod registercluster; +mod peripheral; +pub use peripheral::Peripheral; +mod cpu; +pub use cpu::Cpu; +mod device; +pub use device::Device; -trait ElementExt { - fn get_child_text(&self, k: K) -> Option - where - String: PartialEq; - fn debug(&self); -} - -impl ElementExt for Element { - fn get_child_text(&self, k: K) -> Option - where - String: PartialEq, - { - self.get_child(k).map(|c| try!(c.text.clone())) - } - - fn debug(&self) { - println!("<{}>", self.name); - for c in &self.children { - println!("{}: {:?}", c.name, c.text) - } - println!("", self.name); - } -} - -#[derive(Clone, Debug)] -pub struct Device { - pub name: String, - pub cpu: Option, - pub peripherals: Vec, - pub defaults: Defaults, - // Reserve the right to add more fields to this struct - _extensible: (), -} - -impl Device { - /// Parses a SVD file - /// - /// # Panics - /// - /// If the input is an invalid SVD file (yay, no error handling) - pub fn parse(svd: &str) -> Device { - let tree = &try!(Element::parse(svd.as_bytes())); - - Device { - name: try!(tree.get_child_text("name")), - cpu: tree.get_child("cpu").map(Cpu::parse), - peripherals: try!(tree.get_child("peripherals")) - .children - .iter() - .map(Peripheral::parse) - .collect(), - defaults: Defaults::parse(tree), - _extensible: (), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Endian { - Little, - Big, - Selectable, - Other -} - -impl Endian { - fn parse(tree: &Element) -> Endian { - let text = try!(tree.text.as_ref()); - - match &text[..] { - "little" => Endian::Little, - "big" => Endian::Big, - "selectable" => Endian::Selectable, - "other" => Endian::Other, - _ => panic!("unknown endian variant: {}", text), - } - } -} - - -#[derive(Clone, Debug)] -pub struct Cpu { - pub name: String, - pub revision: String, - pub endian: Endian, - pub mpu_present: bool, - pub fpu_present: bool, - pub nvic_priority_bits: u32, - pub has_vendor_systick: bool, - - // Reserve the right to add more fields to this struct - _extensible: (), -} - -impl Cpu { - fn parse(tree: &Element) -> Cpu { - assert_eq!(tree.name, "cpu"); - - Cpu { - name: try!(tree.get_child_text("name")), - revision: try!(tree.get_child_text("revision")), - endian: Endian::parse(try!(tree.get_child("endian"))), - mpu_present: try!(parse::bool(try!(tree.get_child("mpuPresent")))), - fpu_present: try!(parse::bool(try!(tree.get_child("fpuPresent")))), - nvic_priority_bits: - try!(parse::u32(try!(tree.get_child("nvicPrioBits")))), - has_vendor_systick: - try!(parse::bool(try!(tree.get_child("vendorSystickConfig")))), - - _extensible: (), - } - } - - pub fn is_cortex_m(&self) -> bool { - self.name.starts_with("CM") - } -} -#[derive(Clone, Debug)] -pub struct Peripheral { - pub name: String, - pub group_name: Option, - pub description: Option, - pub base_address: u32, - pub interrupt: Vec, - /// `None` indicates that the `` node is not present - pub registers: Option>>, - pub derived_from: Option, - // Reserve the right to add more fields to this struct - _extensible: (), -} - -impl Peripheral { - fn parse(tree: &Element) -> Peripheral { - assert_eq!(tree.name, "peripheral"); - - Peripheral { - name: try!(tree.get_child_text("name")), - group_name: tree.get_child_text("groupName"), - description: tree.get_child_text("description"), - base_address: try!(parse::u32(try!(tree.get_child("baseAddress")))), - interrupt: tree.children - .iter() - .filter(|t| t.name == "interrupt") - .map(Interrupt::parse) - .collect::>(), - registers: tree.get_child("registers").map(|rs| { - rs.children.iter().map(cluster_register_parse).collect() - }), - derived_from: tree.attributes.get("derivedFrom").map( - |s| { - s.to_owned() - }, - ), - _extensible: (), - } - } -} - -#[derive(Clone, Debug)] -pub struct Interrupt { - pub name: String, - pub description: Option, - pub value: u32, -} - -impl Interrupt { - fn parse(tree: &Element) -> Interrupt { - Interrupt { - name: try!(tree.get_child_text("name")), - description: tree.get_child_text("description"), - value: try!(parse::u32(try!(tree.get_child("value")))), - } - } -} - -#[derive(Clone, Debug)] -pub struct ClusterInfo { - pub name: String, - pub description: String, - pub header_struct_name: String, - pub address_offset: u32, - pub size: Option, - pub access: Option, - pub reset_value: Option, - pub reset_mask: Option, - pub children: Vec>, - // Reserve the right to add more fields to this struct - _extensible: (), -} - -#[derive(Clone, Debug)] -pub struct RegisterInfo { - pub name: String, - pub description: String, - pub address_offset: u32, - pub size: Option, - pub access: Option, - pub reset_value: Option, - pub reset_mask: Option, - /// `None` indicates that the `` node is not present - pub fields: Option>, - pub write_constraint: Option, - // Reserve the right to add more fields to this struct - _extensible: (), -} - -#[derive(Clone, Debug)] -pub struct RegisterClusterArrayInfo { - pub dim: u32, - pub dim_increment: u32, - pub dim_index: Option>, -} - -fn cluster_register_parse(tree: &Element) -> Either { - if tree.name == "register" { - Either::Left(Register::parse(tree)) - } else if tree.name == "cluster" { - Either::Right(Cluster::parse(tree)) - } else { - unreachable!() - } -} - -impl Cluster { - fn parse(tree: &Element) -> Cluster { - assert_eq!(tree.name, "cluster"); - - let info = ClusterInfo::parse(tree); - - if tree.get_child("dimIncrement").is_some() { - let array_info = RegisterClusterArrayInfo::parse(tree); - assert!(info.name.contains("%s")); - if let Some(ref indices) = array_info.dim_index { - assert_eq!(array_info.dim as usize, indices.len()) - } - Cluster::Array(info, array_info) - } else { - Cluster::Single(info) - } - } -} - -#[derive(Clone, Debug)] -pub enum Cluster { - Single(ClusterInfo), - Array(ClusterInfo, RegisterClusterArrayInfo), -} - -impl Deref for Cluster { - type Target = ClusterInfo; - - fn deref(&self) -> &ClusterInfo { - match *self { - Cluster::Single(ref info) => info, - Cluster::Array(ref info, _) => info, - } - } -} - -#[derive(Clone, Debug)] -pub enum Register { - Single(RegisterInfo), - Array(RegisterInfo, RegisterClusterArrayInfo), -} - -impl Deref for Register { - type Target = RegisterInfo; - - fn deref(&self) -> &RegisterInfo { - match *self { - Register::Single(ref info) => info, - Register::Array(ref info, _) => info, - } - } -} - -impl ClusterInfo { - fn parse(tree: &Element) -> ClusterInfo { - ClusterInfo { - name: try!(tree.get_child_text("name")), - description: try!(tree.get_child_text("description")), - header_struct_name: try!(tree.get_child_text("headerStructName")), - address_offset: { - try!(parse::u32(try!(tree.get_child("addressOffset")))) - }, - size: tree.get_child("size").map(|t| try!(parse::u32(t))), - access: tree.get_child("access").map(Access::parse), - reset_value: - tree.get_child("resetValue").map(|t| try!(parse::u32(t))), - reset_mask: - tree.get_child("resetMask").map(|t| try!(parse::u32(t))), - children: tree.children - .iter() - .filter(|t| t.name == "register" || t.name == "cluster") - .map(cluster_register_parse) - .collect(), - _extensible: (), - } - } -} - -impl RegisterInfo { - fn parse(tree: &Element) -> RegisterInfo { - RegisterInfo { - name: try!(tree.get_child_text("name")), - description: try!(tree.get_child_text("description")), - address_offset: { - try!(parse::u32(try!(tree.get_child("addressOffset")))) - }, - size: tree.get_child("size").map(|t| try!(parse::u32(t))), - access: tree.get_child("access").map(Access::parse), - reset_value: - tree.get_child("resetValue").map(|t| try!(parse::u32(t))), - reset_mask: - tree.get_child("resetMask").map(|t| try!(parse::u32(t))), - fields: - tree.get_child("fields") - .map(|fs| fs.children.iter().map(Field::parse).collect()), - write_constraint: tree.get_child("writeConstraint") - .map(WriteConstraint::parse), - _extensible: (), - } - } -} - -impl RegisterClusterArrayInfo { - fn parse(tree: &Element) -> RegisterClusterArrayInfo { - RegisterClusterArrayInfo { - dim: try!(tree.get_child_text("dim").unwrap().parse::()), - dim_increment: try!(tree.get_child("dimIncrement").map(|t| { - try!(parse::u32(t)) - })), - dim_index: tree.get_child("dimIndex").map(|c| { - parse::dim_index(try!(c.text.as_ref())) - }), - } - } -} - -impl Register { - fn parse(tree: &Element) -> Register { - assert_eq!(tree.name, "register"); - - let info = RegisterInfo::parse(tree); - - if tree.get_child("dimIncrement").is_some() { - let array_info = RegisterClusterArrayInfo::parse(tree); - assert!(info.name.contains("%s")); - if let Some(ref indices) = array_info.dim_index { - assert_eq!(array_info.dim as usize, indices.len()) - } - Register::Array(info, array_info) - } else { - Register::Single(info) - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Access { - ReadOnly, - ReadWrite, - ReadWriteOnce, - WriteOnce, - WriteOnly, -} - -impl Access { - fn parse(tree: &Element) -> Access { - let text = try!(tree.text.as_ref()); - - match &text[..] { - "read-only" => Access::ReadOnly, - "read-write" => Access::ReadWrite, - "read-writeOnce" => Access::ReadWriteOnce, - "write-only" => Access::WriteOnly, - "writeOnce" => Access::WriteOnce, - _ => panic!("unknown access variant: {}", text), - } - } -} - -#[derive(Clone, Debug)] -pub struct Field { - pub name: String, - pub description: Option, - pub bit_range: BitRange, - pub access: Option, - pub enumerated_values: Vec, - pub write_constraint: Option, - // Reserve the right to add more fields to this struct - _extensible: (), -} - -impl Field { - fn parse(tree: &Element) -> Field { - assert_eq!(tree.name, "field"); - - Field { - name: try!(tree.get_child_text("name")), - description: tree.get_child_text("description"), - bit_range: BitRange::parse(tree), - access: tree.get_child("access").map(Access::parse), - enumerated_values: tree.children - .iter() - .filter(|t| t.name == "enumeratedValues") - .map(EnumeratedValues::parse) - .collect::>(), - write_constraint: tree.get_child("writeConstraint") - .map(WriteConstraint::parse), - _extensible: (), - } - } -} - -#[derive(Clone, Copy, Debug)] -pub struct BitRange { - pub offset: u32, - pub width: u32, -} - -impl BitRange { - fn parse(tree: &Element) -> BitRange { - let (end, start): (u32, u32) = if let Some(range) = - tree.get_child("bitRange") { - let text = try!(range.text.as_ref()); - - assert!(text.starts_with('[')); - assert!(text.ends_with(']')); - - let mut parts = text[1..text.len() - 1].split(':'); - - (try!(try!(parts.next()).parse()), try!(try!(parts.next()).parse())) - } else if let (Some(lsb), Some(msb)) = - (tree.get_child("lsb"), tree.get_child("msb")) { - (try!(parse::u32(msb)), try!(parse::u32(lsb))) - } else { - return BitRange { - offset: try!(parse::u32(try!(tree.get_child("bitOffset")))), - width: try!(parse::u32(try!(tree.get_child("bitWidth")))), - }; - }; - - BitRange { - offset: start, - width: end - start + 1, - } - } -} - -#[derive(Clone, Copy, Debug)] -pub struct WriteConstraintRange { - pub min: u32, - pub max: u32, -} - -impl WriteConstraintRange { - fn parse(tree: &Element) -> WriteConstraintRange { - WriteConstraintRange { - min: try!(try!(tree.get_child_text("minimum")).parse()), - max: try!(try!(tree.get_child_text("maximum")).parse()), - } - } -} - -#[derive(Clone, Copy, Debug)] -pub enum WriteConstraint { - WriteAsRead(bool), - UseEnumeratedValues(bool), - Range(WriteConstraintRange), -} - -impl WriteConstraint { - fn parse(tree: &Element) -> WriteConstraint { - if tree.children.len() == 1 { - let ref field = tree.children[0].name; - // Write constraint can only be one of the following - match field.as_ref() { - "writeAsRead" => { - WriteConstraint::WriteAsRead( - try!( - tree.get_child(field.as_ref()) - .map(|t| try!(parse::bool(t))) - ), - ) - } - "useEnumeratedValues" => { - WriteConstraint::UseEnumeratedValues( - try!( - tree.get_child(field.as_ref()) - .map(|t| try!(parse::bool(t))) - ), - ) - } - "range" => { - WriteConstraint::Range( - try!( - tree.get_child(field.as_ref()) - .map(WriteConstraintRange::parse) - ), - ) - } - v => panic!("unknown variant: {}", v), - } - } else { - panic!("found more than one element") - } - } -} - -/// Register default properties -#[derive(Clone, Copy, Debug)] -pub struct Defaults { - pub size: Option, - pub reset_value: Option, - pub reset_mask: Option, - pub access: Option, - // Reserve the right to add more fields to this struct - _extensible: (), -} - -impl Defaults { - fn parse(tree: &Element) -> Defaults { - Defaults { - size: tree.get_child("size").map(|t| try!(parse::u32(t))), - reset_value: - tree.get_child("resetValue").map(|t| try!(parse::u32(t))), - reset_mask: - tree.get_child("resetMask").map(|t| try!(parse::u32(t))), - access: tree.get_child("access").map(Access::parse), - _extensible: (), - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Usage { - Read, - Write, - ReadWrite, -} - -impl Usage { - fn parse(tree: &Element) -> Usage { - let text = try!(tree.text.as_ref()); - - match &text[..] { - "read" => Usage::Read, - "write" => Usage::Write, - "read-write" => Usage::ReadWrite, - _ => panic!("unknown usage variant: {}", text), - } - } -} - -#[derive(Clone, Debug)] -pub struct EnumeratedValues { - pub name: Option, - pub usage: Option, - pub derived_from: Option, - pub values: Vec, - // Reserve the right to add more fields to this struct - _extensible: (), -} - -impl EnumeratedValues { - fn parse(tree: &Element) -> EnumeratedValues { - assert_eq!(tree.name, "enumeratedValues"); - - EnumeratedValues { - name: tree.get_child_text("name"), - usage: tree.get_child("usage").map(Usage::parse), - derived_from: tree.attributes - .get(&"derivedFrom".to_owned()) - .map(|s| s.to_owned()), - values: tree.children - .iter() - .filter_map(EnumeratedValue::parse) - .collect(), - _extensible: (), - } - } -} - -#[derive(Clone, Debug)] -pub struct EnumeratedValue { - pub name: String, - pub description: Option, - pub value: Option, - pub is_default: Option, - // Reserve the right to add more fields to this struct - _extensible: (), -} +/// Parses the contents of a SVD file (XML) +pub fn parse(xml: &str) -> Device { + let tree = &try_get_child!(Element::parse(xml.as_bytes())); + Device::parse(tree) +} + +pub fn encode(device: &Device) -> Element { + device.encode() +} + + +#[cfg(test)] +mod tests { + use super::*; + + use std::fs; + use std::process::Command; + use std::fs::File; + use std::io::prelude::*; + use std::path::Path; + + #[test] + fn decode_encode() { + let path = String::from("./examples"); + + let files: Vec = fs::read_dir(&path) + .unwrap() + .map(|res| res.unwrap()) + .filter(|all| !all.metadata().unwrap().is_dir()) + .map(|file| file.file_name().into_string().unwrap()) + .filter(|filename| { + !(filename.starts_with(".") || filename.starts_with("_")) && filename.ends_with(".svd") + }) + .map(|filename| { + String::from(Path::new(&filename).file_stem().unwrap().to_str().unwrap()) + }) + .collect(); + + println!("Files: {:?}", files); + + for name in files { + let source_file = format!("{}/{}.svd", path, name); + let original_file = format!("target/{}-original.svd", name); + let encoded_file = format!("target/{}-encoded.svd", name); + let diff_file = format!("target/{}-diff.svd", name); + + // Load orignal target file + let mut xml = String::new(); + let mut f = fs::File::open(&source_file).unwrap(); + f.read_to_string(&mut xml).unwrap(); + + // Parse device info + let device = parse(&xml); + + // Encode device info + + encode(&device).write(File::create(&encoded_file).unwrap()); + + // Normalize source info + let output = Command::new("xmllint") + .arg("--c14n") + .arg(source_file.clone()) + .output() + .unwrap(); + let mut f = File::create(original_file.clone()).unwrap(); + f.write_all(&output.stdout).unwrap(); + + let output = Command::new("xmllint") + .arg("--format") + .arg(source_file.clone()) + .output() + .unwrap(); + let mut f = File::create(original_file.clone()).unwrap(); + f.write_all(&output.stdout).unwrap(); + + // Normalise encoded info + let output = Command::new("xmllint") + .arg("--c14n") + .arg(encoded_file.clone()) + .output() + .unwrap(); + let mut f = File::create(encoded_file.clone()).unwrap(); + f.write_all(&output.stdout).unwrap(); + + let output = Command::new("xmllint") + .arg("--format") + .arg(encoded_file.clone()) + .output() + .unwrap(); + let mut f = File::create(encoded_file.clone()).unwrap(); + f.write_all(&output.stdout).unwrap(); + + // Diff normalised source and output + let output = Command::new("diff") + .arg(original_file) + .arg(encoded_file) + .output() + .unwrap(); + let mut f = File::create(diff_file).unwrap(); + f.write_all(&output.stdout).unwrap(); -impl EnumeratedValue { - fn parse(tree: &Element) -> Option { - if tree.name != "enumeratedValue" { - return None; } - - Some( - EnumeratedValue { - name: try!(tree.get_child_text("name")), - description: tree.get_child_text("description"), - value: tree.get_child("value").map(|t| try!(parse::u32(t))), - is_default: tree.get_child_text("isDefault").map( - |t| { - try!(t.parse()) - }, - ), - _extensible: (), - }, - ) } } diff --git a/src/parse.rs b/src/parse.rs index aeb507f0..2f9eb95a 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,7 +1,8 @@ use xmltree::Element; + pub fn u32(tree: &Element) -> Option { - let text = try!(tree.text.as_ref()); + let text = try_get_child!(tree.text.as_ref()); if text.starts_with("0x") || text.starts_with("0X") { u32::from_str_radix(&text["0x".len()..], 16).ok() @@ -16,19 +17,19 @@ pub fn u32(tree: &Element) -> Option { } pub fn bool(tree: &Element) -> Option { - let text = try!(tree.text.as_ref()); + let text = try_get_child!(tree.text.as_ref()); match text.as_ref() { "0" => Some(false), "1" => Some(true), - _ => text.parse::().ok() + _ => text.parse::().ok(), } } pub fn dim_index(text: &str) -> Vec { if text.contains('-') { let mut parts = text.splitn(2, '-'); - let start = try!(try!(parts.next()).parse::()); - let end = try!(try!(parts.next()).parse::()) + 1; + let start = try_get_child!(try_get_child!(parts.next()).parse::()); + let end = try_get_child!(try_get_child!(parts.next()).parse::()) + 1; (start..end).map(|i| i.to_string()).collect() } else if text.contains(',') { diff --git a/src/peripheral.rs b/src/peripheral.rs new file mode 100644 index 00000000..b8106442 --- /dev/null +++ b/src/peripheral.rs @@ -0,0 +1,150 @@ + +use std::collections::HashMap; + +use xmltree::Element; +use either::Either; + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, new_element}; +use parse; + +use interrupt::Interrupt; +use register::Register; +use cluster::Cluster; +use addressblock::AddressBlock; +use registercluster::cluster_register_parse; + + +#[derive(Clone, Debug)] +pub struct Peripheral { + pub name: String, + pub version: Option, + pub display_name: Option, + pub group_name: Option, + pub description: Option, + pub base_address: u32, + pub address_block: Option, + pub interrupt: Vec, + /// `None` indicates that the `` node is not present + pub registers: Option>>, + pub derived_from: Option, + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl ParseElem for Peripheral { + fn parse(tree: &Element) -> Peripheral { + assert_eq!(tree.name, "peripheral"); + + Peripheral { + name: try_get_child!(tree.get_child_text("name")), + version: tree.get_child_text("version"), + display_name: tree.get_child_text("displayName"), + group_name: tree.get_child_text("groupName"), + description: tree.get_child_text("description"), + base_address: try_get_child!(parse::u32(try_get_child!(tree.get_child("baseAddress")))), + address_block: tree.get_child("addressBlock").map(AddressBlock::parse), + interrupt: tree.children + .iter() + .filter(|t| t.name == "interrupt") + .map(Interrupt::parse) + .collect::>(), + registers: tree.get_child("registers").map(|rs| { + rs.children.iter().map(cluster_register_parse).collect() + }), + derived_from: tree.attributes.get("derivedFrom").map(|s| s.to_owned()), + _extensible: (), + } + } +} + +impl EncodeElem for Peripheral { + fn encode(&self) -> Element { + let mut elem = Element { + name: String::from("peripheral"), + attributes: HashMap::new(), + children: vec![new_element("name", Some(self.name.clone()))], + text: None, + }; + + match self.version { + Some(ref v) => { + elem.children.push( + new_element("version", Some(format!("{}", v))), + ); + } + None => (), + }; + match self.display_name { + Some(ref v) => { + elem.children.push(new_element( + "displayName", + Some(format!("{}", v)), + )); + } + None => (), + }; + match self.group_name { + Some(ref v) => { + elem.children.push(new_element( + "groupName", + Some(format!("{}", v)), + )); + } + None => (), + }; + match self.description { + Some(ref v) => { + elem.children.push(new_element( + "description", + Some(format!("{}", v)), + )); + } + None => (), + }; + elem.children.push(new_element( + "baseAddress", + Some(format!("0x{:.08x}", self.base_address)), + )); + match self.address_block { + Some(ref v) => { + elem.children.push(v.encode()); + } + None => (), + }; + + elem.children.append(&mut self.interrupt + .iter() + .map(Interrupt::encode) + .collect()); + match self.registers { + Some(ref v) => { + elem.children.push(Element { + name: String::from("registers"), + attributes: HashMap::new(), + children: v.iter() + .map(|&ref e| if e.is_left() { + e.clone().left().unwrap().encode() + } else { + e.clone().right().unwrap().encode() + }) + .collect(), + text: None, + }); + } + None => (), + }; + + match self.derived_from { + Some(ref v) => { + elem.attributes.insert( + String::from("derivedFrom"), + format!("{}", v), + ); + } + None => (), + } + + elem + } +} diff --git a/src/register.rs b/src/register.rs new file mode 100644 index 00000000..b5c7ed62 --- /dev/null +++ b/src/register.rs @@ -0,0 +1,62 @@ + +use std::ops::Deref; + +use xmltree::Element; + +use helpers::{ParseElem, EncodeElem}; + +use registerinfo::RegisterInfo; +use registerarrayinfo::RegisterArrayInfo; + + +#[derive(Clone, Debug, PartialEq)] +pub enum Register { + Single(RegisterInfo), + Array(RegisterInfo, RegisterArrayInfo), +} + + +impl Deref for Register { + type Target = RegisterInfo; + + fn deref(&self) -> &RegisterInfo { + match *self { + Register::Single(ref info) => info, + Register::Array(ref info, _) => info, + } + } +} + + +impl ParseElem for Register { + // TODO handle "clusters", return `Register` not an `Option` + fn parse(tree: &Element) -> Register { + assert_eq!(tree.name, "register"); + + let info = RegisterInfo::parse(tree); + + if tree.get_child("dimIncrement").is_some() { + let array_info = RegisterArrayInfo::parse(tree); + assert!(info.name.contains("%s")); + if let Some(ref indices) = array_info.dim_index { + assert_eq!(array_info.dim as usize, indices.len()) + } + Register::Array(info, array_info) + } else { + Register::Single(info) + } + } +} + +impl EncodeElem for Register { + fn encode(&self) -> Element { + match *self { + Register::Single(ref info) => info.encode(), + Register::Array(ref info, ref _array_info) => { + // TODO: fix this (does not encode array stuff) + // Not even slightly sure what to do here + info.encode() + } + } + } +} diff --git a/src/registerarrayinfo.rs b/src/registerarrayinfo.rs new file mode 100644 index 00000000..a7023a20 --- /dev/null +++ b/src/registerarrayinfo.rs @@ -0,0 +1,41 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem}; +use parse; + + +#[derive(Clone, Debug, PartialEq)] +pub struct RegisterArrayInfo { + pub dim: u32, + pub dim_increment: u32, + pub dim_index: Option>, +} + +impl ParseElem for RegisterArrayInfo { + fn parse(tree: &Element) -> RegisterArrayInfo { + RegisterArrayInfo { + dim: try_get_child!(tree.get_child_text("dim").unwrap().parse::()), + dim_increment: try_get_child!(tree.get_child("dimIncrement").map(|t| { + try_get_child!(parse::u32(t)) + })), + dim_index: tree.get_child("dimIndex").map(|c| { + parse::dim_index(try_get_child!(c.text.as_ref())) + }), + } + } +} + +impl EncodeElem for RegisterArrayInfo { + fn encode(&self) -> Element { + Element { + name: String::from("NOPE"), + attributes: HashMap::new(), + children: Vec::new(), + text: None, + } + } +} diff --git a/src/registercluster.rs b/src/registercluster.rs new file mode 100644 index 00000000..c1ccac09 --- /dev/null +++ b/src/registercluster.rs @@ -0,0 +1,17 @@ + +use xmltree::Element; +use either::Either; + +use helpers::ParseElem; +use register::Register; +use cluster::Cluster; + +pub fn cluster_register_parse(tree: &Element) -> Either { + if tree.name == "register" { + Either::Left(Register::parse(tree)) + } else if tree.name == "cluster" { + Either::Right(Cluster::parse(tree)) + } else { + unreachable!() + } +} diff --git a/src/registerclusterarrayinfo.rs b/src/registerclusterarrayinfo.rs new file mode 100644 index 00000000..e7de398a --- /dev/null +++ b/src/registerclusterarrayinfo.rs @@ -0,0 +1,33 @@ + +use xmltree::Element; + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, new_element}; +use parse; + +#[derive(Clone, Debug, PartialEq)] +pub struct RegisterClusterArrayInfo { + pub dim: u32, + pub dim_increment: u32, + pub dim_index: Option>, +} + +impl ParseElem for RegisterClusterArrayInfo { + fn parse(tree: &Element) -> RegisterClusterArrayInfo { + RegisterClusterArrayInfo { + dim: try_get_child!(tree.get_child_text("dim").unwrap().parse::()), + dim_increment: try_get_child!(tree.get_child("dimIncrement").map(|t| { + try_get_child!(parse::u32(t)) + })), + dim_index: tree.get_child("dimIndex").map(|c| { + parse::dim_index(try_get_child!(c.text.as_ref())) + }), + } + } +} + +impl EncodeElem for RegisterClusterArrayInfo { + fn encode(&self) -> Element { + new_element("FAKE", None) + } +} diff --git a/src/registerinfo.rs b/src/registerinfo.rs new file mode 100644 index 00000000..c1d990b1 --- /dev/null +++ b/src/registerinfo.rs @@ -0,0 +1,205 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, new_element}; +use parse; + +use Field; +use access::Access; +use writeconstraint::WriteConstraint; + + +#[derive(Clone, Debug, PartialEq)] +pub struct RegisterInfo { + pub name: String, + pub description: String, + pub address_offset: u32, + pub size: Option, + pub access: Option, + pub reset_value: Option, + pub reset_mask: Option, + /// `None` indicates that the `` node is not present + pub fields: Option>, + pub write_constraint: Option, + // Reserve the right to add more fields to this struct + pub(crate) _extensible: (), +} + +impl ParseElem for RegisterInfo { + fn parse(tree: &Element) -> RegisterInfo { + RegisterInfo { + name: try_get_child!(tree.get_child_text("name")), + description: try_get_child!(tree.get_child_text("description")), + address_offset: { + try_get_child!(parse::u32(try_get_child!(tree.get_child("addressOffset")))) + }, + size: tree.get_child("size").map( + |t| try_get_child!(parse::u32(t)), + ), + access: tree.get_child("access").map(Access::parse), + reset_value: tree.get_child("resetValue").map(|t| { + try_get_child!(parse::u32(t)) + }), + reset_mask: tree.get_child("resetMask").map( + |t| try_get_child!(parse::u32(t)), + ), + fields: tree.get_child("fields").map(|fs| { + fs.children.iter().map(Field::parse).collect() + }), + write_constraint: tree.get_child("writeConstraint").map( + WriteConstraint::parse, + ), + _extensible: (), + } + } +} + +impl EncodeElem for RegisterInfo { + fn encode(&self) -> Element { + let mut elem = Element { + name: String::from("register"), + attributes: HashMap::new(), + children: vec![ + new_element("name", Some(self.name.clone())), + new_element("description", Some(self.description.clone())), + new_element( + "addressOffset", + Some(format!("0x{:x}", self.address_offset)) + ), + ], + text: None, + }; + + match self.size { + Some(ref v) => { + elem.children.push( + new_element("size", Some(format!("{}", v))), + ); + } + None => (), + }; + + match self.access { + Some(ref v) => { + elem.children.push(v.encode()); + } + None => (), + }; + + match self.reset_value { + Some(ref v) => { + elem.children.push(new_element( + "resetValue", + Some(format!("0x{:08.x}", v)), + )); + } + None => (), + }; + + match self.reset_mask { + Some(ref v) => { + elem.children.push(new_element( + "resetMask", + Some(format!("0x{:08.x}", v)), + )); + } + None => (), + }; + + match self.fields { + Some(ref v) => { + let fields = Element { + name: String::from("fields"), + attributes: HashMap::new(), + children: v.iter().map(Field::encode).collect(), + text: None, + }; + elem.children.push(fields); + } + None => (), + }; + + match self.write_constraint { + Some(ref v) => { + elem.children.push(v.encode()); + } + None => (), + }; + + elem + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use bitrange::*; + + #[test] + fn decode_encode() { + let types = vec![ + ( + RegisterInfo { + name: String::from("WRITECTRL"), + description: String::from("Write Control Register"), + address_offset: 8, + size: Some(32), + access: Some(Access::ReadWrite), + reset_value: Some(0x00000000), + reset_mask: Some(0x00000023), + fields: Some(vec![ + Field { + name: String::from("WREN"), + description: Some(String::from("Enable Write/Erase Controller")), + bit_range: BitRange { + offset: 0, + width: 1, + range_type: BitRangeType::OffsetWidth, + }, + access: Some(Access::ReadWrite), + enumerated_values: Vec::new(), + write_constraint: None, + _extensible: (), + }, + ]), + write_constraint: None, + _extensible: (), + }, + String::from( + " + + WRITECTRL + Write Control Register + 0x8 + 32 + read-write + 0x00000000 + 0x00000023 + + + WREN + Enable Write/Erase Controller + 0 + 1 + read-write + + + + ", + ) + ), + ]; + + for (a, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let v = RegisterInfo::parse(tree1); + assert_eq!(v, a, "Parsing `{}` expected `{:?}`", s, a); + let tree2 = &v.encode(); + assert_eq!(tree1, tree2, "Encoding {:?} expected {}", a, s); + } + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 00000000..dfd1133c --- /dev/null +++ b/src/types.rs @@ -0,0 +1,101 @@ + +// Encoding type +#[derive(Debug)] +enum Encoding { + Bin, + Oct, + Dec, + Hex, +} + +// Uint type for less lossy encoding/decoding +#[derive(Debug)] +pub struct Uint { + pub value: u32, + width: usize, + encoding: Encoding, +} + +// Equality based only on value +impl PartialEq for Uint { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl Uint { + pub fn parse(text: &str) -> Uint { + if text.starts_with("0x") || text.starts_with("0X") { + Uint { + value: u32::from_str_radix(&text["0x".len()..], 16).unwrap(), + width: text.len()-2, + encoding: Encoding::Hex, + } + } else if text.starts_with('#') { + Uint { + value: u32::from_str_radix(&str::replace(&text["#".len()..], "x", "0"), 2).unwrap(), + width: text.len()-1, + encoding: Encoding::Bin, + } + } else if text.starts_with('0') { + Uint { + value: u32::from_str_radix(text, 8).unwrap(), + width: text.len()-1, + encoding: Encoding::Oct, + } + } else { + Uint { + value: text.parse().unwrap(), + width: text.len(), + encoding: Encoding::Dec, + } + } + } + + pub fn encode(&self) -> String { + match self.encoding { + Encoding::Dec => { + let base = &format!("{}", self.value); + let packing = String::from_utf8(vec!['0' as u8; self.width - base.len()]).unwrap(); + format!("{}{}", packing, base) + }, + Encoding::Hex => { + let base = format!("{:.x}", self.value); + let packing = String::from_utf8(vec!['0' as u8; self.width - base.len()]).unwrap(); + format!("0x{}{}", packing, base) + }, + Encoding::Oct => { + let base = &format!("{:o}", self.value); + let packing = String::from_utf8(vec!['0' as u8; self.width - base.len()]).unwrap(); + format!("0{}{}", packing, base) + }, + Encoding::Bin => { + let base = format!("{:b}", self.value); + let packing = String::from_utf8(vec!['0' as u8; self.width - base.len()]).unwrap(); + format!("#{}{}", packing, base) + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn uint_decode_encode() { + let tests = vec![ + ("104", Uint{value: 104, width: 3, encoding: Encoding::Dec}), + ("0x013a", Uint{value: 314, width: 4, encoding: Encoding::Hex}), + ("01232", Uint{value: 666, width: 4, encoding: Encoding::Oct}), + ("#0101", Uint{value: 5, width: 4, encoding: Encoding::Bin}), + ]; + + for (text, value) in tests { + let a = Uint::parse(text); + assert_eq!(a, value); + let b = value.encode(); + assert_eq!(b, text); + } + } +} \ No newline at end of file diff --git a/src/usage.rs b/src/usage.rs new file mode 100644 index 00000000..b66a5e29 --- /dev/null +++ b/src/usage.rs @@ -0,0 +1,66 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use helpers::{ParseElem, EncodeElem}; + + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Usage { + Read, + Write, + ReadWrite, +} + +impl ParseElem for Usage { + fn parse(tree: &Element) -> Usage { + let text = try_get_child!(tree.text.as_ref()); + + match &text[..] { + "read" => Usage::Read, + "write" => Usage::Write, + "read-write" => Usage::ReadWrite, + _ => panic!("unknown usage variant: {}", text), + } + } +} + +impl EncodeElem for Usage { + fn encode(&self) -> Element { + let text = match *self { + Usage::Read => String::from("read"), + Usage::Write => String::from("write"), + Usage::ReadWrite => String::from("read-write"), + }; + + Element { + name: String::from("usage"), + attributes: HashMap::new(), + children: Vec::new(), + text: Some(text), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let types = vec![ + (Usage::Read, String::from("read")), + (Usage::Write, String::from("write")), + (Usage::ReadWrite, String::from("read-write")), + ]; + + for (e, s) in types { + let tree1 = &try_get_child!(Element::parse(s.as_bytes())); + let elem = Usage::parse(tree1); + assert_eq!(elem, e, "Parsing `{}` expected `{:?}`", s, e); + let tree2 = &elem.encode(); + assert_eq!(tree1, tree2, "Encoding {:?} expected {}", e, s); + } + } +} diff --git a/src/writeconstraint.rs b/src/writeconstraint.rs new file mode 100644 index 00000000..63acc6c2 --- /dev/null +++ b/src/writeconstraint.rs @@ -0,0 +1,91 @@ + +use std::collections::HashMap; + +use xmltree::Element; + +use helpers::{ParseElem, EncodeElem, new_element}; +use parse; + +use writeconstraintrange::WriteConstraintRange; + + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WriteConstraint { + WriteAsRead(bool), + UseEnumeratedValues(bool), + Range(WriteConstraintRange), +} + +impl ParseElem for WriteConstraint { + fn parse(tree: &Element) -> WriteConstraint { + if tree.children.len() == 1 { + let ref field = tree.children[0].name; + // Write constraint can only be one of the following + match field.as_ref() { + "writeAsRead" => { + WriteConstraint::WriteAsRead(try_get_child!(tree.get_child(field.as_ref()).map(|t| { + try_get_child!(parse::bool(t)) + }))) + } + "useEnumeratedValues" => { + WriteConstraint::UseEnumeratedValues(try_get_child!(tree.get_child(field.as_ref()).map(|t| { + try_get_child!(parse::bool(t)) + }))) + } + "range" => { + WriteConstraint::Range(try_get_child!(tree.get_child(field.as_ref()).map( + WriteConstraintRange::parse, + ))) + } + v => panic!("unknown variant: {}", v), + } + } else { + panic!("found more than one element") + } + } +} + +impl EncodeElem for WriteConstraint { + fn encode(&self) -> Element { + let v = match *self { + WriteConstraint::WriteAsRead(v) => new_element("writeAsRead", Some(format!("{}", v))), + WriteConstraint::UseEnumeratedValues(v) => new_element("useEnumeratedValues", Some(format!("{}", v))), + WriteConstraint::Range(v) => v.encode(), + }; + + Element { + name: String::from("WriteConstraint"), + attributes: HashMap::new(), + children: vec![v], + text: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_encode() { + let examples = vec![ + ( + String::from( + "true", + ), + WriteConstraint::WriteAsRead(true) + ), + ]; + + for (example, expected) in examples { + let tree1 = &try_get_child!(Element::parse(example.as_bytes())); + + let parsed = WriteConstraint::parse(tree1); + assert_eq!(parsed, expected, "Parsing tree failed"); + + let tree2 = &parsed.encode(); + assert_eq!(tree1, tree2, "Encoding value failed"); + } + + } +} diff --git a/src/writeconstraintrange.rs b/src/writeconstraintrange.rs new file mode 100644 index 00000000..fe92ad65 --- /dev/null +++ b/src/writeconstraintrange.rs @@ -0,0 +1,38 @@ + +use std::collections::HashMap; + +use xmltree::Element; + + +use elementext::ElementExt; +use helpers::{ParseElem, EncodeElem, new_element}; + + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct WriteConstraintRange { + pub min: u32, + pub max: u32, +} + +impl ParseElem for WriteConstraintRange { + fn parse(tree: &Element) -> WriteConstraintRange { + WriteConstraintRange { + min: try_get_child!(try_get_child!(tree.get_child_text("minimum")).parse()), + max: try_get_child!(try_get_child!(tree.get_child_text("maximum")).parse()), + } + } +} + +impl EncodeElem for WriteConstraintRange { + fn encode(&self) -> Element { + Element { + name: String::from("range"), + attributes: HashMap::new(), + children: vec![ + new_element("min", Some(format!("0x{:08.x}", self.min))), + new_element("max", Some(format!("0x{:08.x}", self.max))), + ], + text: None, + } + } +}