Skip to main content

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