svd_parser/
lib.rs

1//! CMSIS-SVD file parser
2//!
3//! # Usage
4//!
5//! ``` no_run
6//! use svd_parser as svd;
7//!
8//! use std::fs::File;
9//! use std::io::Read;
10//!
11//! let xml = &mut String::new();
12//! File::open("STM32F30x.svd").unwrap().read_to_string(xml);
13//!
14//! println!("{:?}", svd::parse(xml));
15//! ```
16//!
17//! # References
18//!
19//! - [SVD Schema file](https://www.keil.com/pack/doc/CMSIS/SVD/html/schema_1_2_gr.html)
20//! - [SVD file database](https://github.com/posborne/cmsis-svd/tree/master/data)
21//! - [Sample SVD file](https://www.keil.com/pack/doc/CMSIS/SVD/html/svd_Example_pg.html)
22//!
23//! Parse traits.
24//! These support parsing of SVD types from XML
25
26pub use svd::ValidateLevel;
27pub use svd_rs as svd;
28
29pub use anyhow::Context;
30use roxmltree::{Document, Node, NodeId};
31// ElementExt extends XML elements with useful methods
32pub mod elementext;
33use crate::elementext::ElementExt;
34// Types defines simple types and parse/encode implementations
35pub mod types;
36
37#[derive(Clone, Copy, Debug, Default)]
38#[non_exhaustive]
39/// Advanced parser options
40pub struct Config {
41    /// SVD error check level
42    pub validate_level: ValidateLevel,
43    #[cfg(feature = "expand")]
44    /// Expand arrays and resolve derivedFrom
45    // TODO: split it on several independent options
46    pub expand: bool,
47    #[cfg(feature = "expand")]
48    /// Derive register properties from parents
49    pub expand_properties: bool,
50    /// Skip parsing and emitting `enumeratedValues` and `writeConstraint` in `Field`
51    pub ignore_enums: bool,
52}
53
54impl Config {
55    /// SVD error check level
56    pub fn validate_level(mut self, lvl: ValidateLevel) -> Self {
57        self.validate_level = lvl;
58        self
59    }
60
61    #[cfg(feature = "expand")]
62    /// Expand arrays and derive
63    pub fn expand(mut self, val: bool) -> Self {
64        self.expand = val;
65        self
66    }
67
68    #[cfg(feature = "expand")]
69    /// Takes register `size`, `access`, `reset_value` and `reset_mask`
70    /// from peripheral or device properties if absent in register
71    pub fn expand_properties(mut self, val: bool) -> Self {
72        self.expand_properties = val;
73        self
74    }
75
76    /// Skip parsing `enumeratedValues` and `writeConstraint` in `Field`
77    pub fn ignore_enums(mut self, val: bool) -> Self {
78        self.ignore_enums = val;
79        self
80    }
81}
82
83/// Parse trait allows SVD objects to be parsed from XML elements.
84pub trait Parse {
85    /// Object returned by parse method
86    type Object;
87    /// Parsing error
88    type Error;
89    /// Advanced parse options
90    type Config;
91    /// Parse an XML/SVD element into it's corresponding `Object`.
92    fn parse(elem: &Node, config: &Self::Config) -> Result<Self::Object, Self::Error>;
93}
94
95/// Parses an optional child element with the provided name and Parse function
96/// Returns an none if the child doesn't exist, Ok(Some(e)) if parsing succeeds,
97/// and Err() if parsing fails.
98pub fn optional<T>(n: &str, e: &Node, config: &T::Config) -> Result<Option<T::Object>, SVDErrorAt>
99where
100    T: Parse<Error = SVDErrorAt>,
101{
102    let child = match e.get_child(n) {
103        Some(c) => c,
104        None => return Ok(None),
105    };
106
107    match T::parse(&child, config) {
108        Ok(r) => Ok(Some(r)),
109        Err(e) => Err(e),
110    }
111}
112
113use crate::svd::Device;
114/// Parses the contents of an SVD (XML) string
115pub fn parse(xml: &str) -> anyhow::Result<Device> {
116    parse_with_config(xml, &Config::default())
117}
118/// Parses the contents of an SVD (XML) string
119pub fn parse_with_config(xml: &str, config: &Config) -> anyhow::Result<Device> {
120    fn get_name<'a>(node: &'a Node) -> Option<&'a str> {
121        node.children()
122            .find(|t| t.has_tag_name("name"))
123            .and_then(|t| t.text())
124    }
125
126    let xml = trim_utf8_bom(xml);
127    let tree = Document::parse(xml)?;
128    let root = tree.root();
129    let xmldevice = root
130        .get_child("device")
131        .ok_or_else(|| SVDError::MissingTag("device".to_string()).at(root.id()))?;
132
133    #[allow(unused_mut)]
134    let mut device = match Device::parse(&xmldevice, config) {
135        Ok(o) => Ok(o),
136        Err(e) => {
137            let id = e.id;
138            let node = tree.get_node(id).unwrap();
139            let pos = tree.text_pos_at(node.range().start);
140            let tagname = node.tag_name().name();
141            let mut res = Err(e.into());
142            if tagname.is_empty() {
143                res = res.with_context(|| format!("at {}", pos))
144            } else if let Some(name) = get_name(&node) {
145                res = res.with_context(|| format!("Parsing {} `{}` at {}", tagname, name, pos))
146            } else {
147                res = res.with_context(|| format!("Parsing unknown {} at {}", tagname, pos))
148            }
149            for parent in node.ancestors().skip(1) {
150                if parent.id() == NodeId::new(0) {
151                    break;
152                }
153                let tagname = parent.tag_name().name();
154                match tagname {
155                    "device" | "peripheral" | "register" | "field" | "enumeratedValue"
156                    | "interrupt" => {
157                        if let Some(name) = get_name(&parent) {
158                            res = res.with_context(|| format!("In {} `{}`", tagname, name));
159                        } else {
160                            res = res.with_context(|| format!("In unknown {}", tagname));
161                        }
162                    }
163                    _ => {}
164                }
165            }
166            res
167        }
168    }?;
169
170    #[cfg(feature = "expand")]
171    if config.expand_properties {
172        expand::expand_properties(&mut device);
173    }
174
175    #[cfg(feature = "expand")]
176    if config.expand {
177        device = expand::expand(&device)?;
178    }
179    Ok(device)
180}
181
182/// Return the &str trimmed UTF-8 BOM if the input &str contains the BOM.
183fn trim_utf8_bom(s: &str) -> &str {
184    if s.len() > 2 && s.as_bytes().starts_with(b"\xef\xbb\xbf") {
185        &s[3..]
186    } else {
187        s
188    }
189}
190
191mod array;
192use array::parse_array;
193
194mod access;
195mod addressblock;
196mod bitrange;
197mod cluster;
198mod cpu;
199mod datatype;
200mod device;
201mod dimelement;
202mod endian;
203mod enumeratedvalue;
204mod enumeratedvalues;
205mod field;
206mod interrupt;
207mod modifiedwritevalues;
208mod peripheral;
209mod protection;
210mod readaction;
211mod register;
212mod registercluster;
213mod registerproperties;
214mod usage;
215mod writeconstraint;
216
217#[cfg(feature = "expand")]
218pub mod expand;
219
220#[cfg(feature = "expand")]
221pub use expand::{expand, expand_properties};
222/// SVD parse Errors.
223#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
224pub enum SVDError {
225    #[error("{0}")]
226    Svd(#[from] svd::SvdError),
227    #[error("Expected a <{0}> tag, found none")]
228    MissingTag(String),
229    #[error("Expected content in <{0}> tag, found none")]
230    EmptyTag(String),
231    #[error("Failed to parse `{0}`")]
232    ParseInt(#[from] std::num::ParseIntError),
233    #[error("Unknown endianness `{0}`")]
234    UnknownEndian(String),
235    #[error("unknown access variant '{0}' found")]
236    UnknownAccessType(String),
237    #[error("Bit range invalid, {0:?}")]
238    InvalidBitRange(bitrange::InvalidBitRange),
239    #[error("Unknown write constraint")]
240    UnknownWriteConstraint,
241    #[error("Multiple wc found")]
242    MoreThanOneWriteConstraint,
243    #[error("Unknown usage variant")]
244    UnknownUsageVariant,
245    #[error("Unknown usage variant for addressBlock")]
246    UnknownAddressBlockUsageVariant,
247    #[error("Expected a <{0}>, found ...")]
248    NotExpectedTag(String),
249    #[error("Invalid RegisterCluster (expected register or cluster), found {0}")]
250    InvalidRegisterCluster(String),
251    #[error("Invalid datatype variant, found {0}")]
252    InvalidDatatype(String),
253    #[error("Invalid modifiedWriteValues variant, found {0}")]
254    InvalidModifiedWriteValues(String),
255    #[error("Invalid readAction variant, found {0}")]
256    InvalidReadAction(String),
257    #[error("Invalid protection variant, found {0}")]
258    InvalidProtection(String),
259    #[error("The content of the element could not be parsed to a boolean value {0}: {1}")]
260    InvalidBooleanValue(String, core::str::ParseBoolError),
261    #[error("dimIndex tag must contain {0} indexes, found {1}")]
262    IncorrectDimIndexesCount(usize, usize),
263    #[error("Failed to parse dimIndex")]
264    DimIndexParse,
265    #[error("Name `{0}` in tag `{1}` is missing a %s placeholder")]
266    MissingPlaceholder(String, String),
267}
268
269#[derive(Clone, Debug, PartialEq)]
270pub struct SVDErrorAt {
271    error: SVDError,
272    id: NodeId,
273}
274
275impl std::fmt::Display for SVDErrorAt {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        self.error.fmt(f)
278    }
279}
280
281impl std::error::Error for SVDErrorAt {}
282
283impl SVDError {
284    pub fn at(self, id: NodeId) -> SVDErrorAt {
285        SVDErrorAt { error: self, id }
286    }
287}
288
289pub(crate) fn check_has_placeholder(name: &str, tag: &str) -> Result<(), SVDError> {
290    if name.contains("%s") {
291        Ok(())
292    } else {
293        Err(SVDError::MissingPlaceholder(
294            name.to_string(),
295            tag.to_string(),
296        ))
297    }
298}
299
300#[test]
301fn test_trim_utf8_bom_from_str() {
302    // UTF-8 BOM + "xyz"
303    let bom_str = std::str::from_utf8(b"\xef\xbb\xbfxyz").unwrap();
304    assert_eq!("xyz", trim_utf8_bom(bom_str));
305    assert_eq!("xyz", trim_utf8_bom("xyz"));
306}