1#![cfg_attr(docsrs, feature(doc_cfg))]
2mod builders;
8#[cfg(feature = "fetch")]
9mod fetch;
10mod parsers;
11mod util;
12
13#[cfg(feature = "fetch")]
14pub use fetch::fetch_and_load_xml;
15
16use quick_xml::Reader;
17use quick_xml::events::{BytesStart, Event};
18use serde::{Deserialize, Serialize};
19use thiserror::Error;
20
21use parsers::{
22 parse_boolean, parse_category, parse_category_empty, parse_command, parse_command_empty,
23 parse_converter, parse_enum, parse_float, parse_int_converter, parse_integer, parse_string,
24 parse_struct_reg, parse_swissknife,
25};
26use util::{attribute_value, skip_element};
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub enum EnumValueSrc {
31 Literal(i64),
33 FromNode(String),
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct EnumEntryDecl {
40 pub name: String,
42 pub value: EnumValueSrc,
44 pub display_name: Option<String>,
46}
47
48#[derive(Debug, Error)]
49#[non_exhaustive]
50pub enum XmlError {
51 #[error("xml: {0}")]
52 Xml(String),
53 #[error("invalid descriptor: {0}")]
54 Invalid(String),
55 #[error("transport: {0}")]
56 Transport(String),
57 #[error("unsupported URL: {0}")]
58 Unsupported(String),
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63pub enum AccessMode {
64 RO,
66 WO,
68 RW,
70}
71
72impl AccessMode {
73 pub(crate) fn parse(value: &str) -> Result<Self, XmlError> {
74 match value.trim().to_ascii_uppercase().as_str() {
75 "RO" => Ok(AccessMode::RO),
76 "WO" => Ok(AccessMode::WO),
77 "RW" => Ok(AccessMode::RW),
78 other => Err(XmlError::Invalid(format!("unknown access mode: {other}"))),
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub enum Addressing {
86 Fixed { address: u64, len: u32 },
88 BySelector {
90 selector: String,
92 map: Vec<(String, (u64, u32))>,
94 },
95 Indirect {
97 p_address_node: String,
99 len: u32,
101 },
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106pub enum ByteOrder {
107 Little,
109 Big,
111}
112
113impl ByteOrder {
114 pub(crate) fn parse(tag: &str) -> Option<Self> {
115 match tag.trim().to_ascii_lowercase().as_str() {
116 "littleendian" => Some(ByteOrder::Little),
117 "bigendian" => Some(ByteOrder::Big),
118 _ => None,
119 }
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
125pub struct BitField {
126 pub bit_offset: u16,
128 pub bit_length: u16,
130 pub byte_order: ByteOrder,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
136pub enum SkOutput {
137 Integer,
140 #[default]
143 Float,
144}
145
146impl SkOutput {
147 pub(crate) fn parse(tag: &str) -> Option<Self> {
148 match tag.trim().to_ascii_lowercase().as_str() {
149 "integer" => Some(SkOutput::Integer),
150 "float" => Some(SkOutput::Float),
151 _ => None,
152 }
153 }
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct SwissKnifeDecl {
159 pub name: String,
161 pub expr: String,
163 pub variables: Vec<(String, String)>,
165 pub output: SkOutput,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct ConverterDecl {
175 pub name: String,
177 pub p_value: String,
179 pub formula_to: String,
181 pub formula_from: String,
183 pub variables_to: Vec<(String, String)>,
185 pub variables_from: Vec<(String, String)>,
187 pub unit: Option<String>,
189 pub output: SkOutput,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct IntConverterDecl {
196 pub name: String,
198 pub p_value: String,
200 pub formula_to: String,
202 pub formula_from: String,
204 pub variables_to: Vec<(String, String)>,
206 pub variables_from: Vec<(String, String)>,
208 pub unit: Option<String>,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct StringDecl {
215 pub name: String,
217 pub addressing: Addressing,
219 pub access: AccessMode,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub enum NodeDecl {
226 Integer {
228 name: String,
230 addressing: Option<Addressing>,
232 len: u32,
234 access: AccessMode,
236 min: i64,
238 max: i64,
240 inc: Option<i64>,
242 unit: Option<String>,
244 bitfield: Option<BitField>,
246 selectors: Vec<String>,
248 selected_if: Vec<(String, Vec<String>)>,
250 pvalue: Option<String>,
252 p_max: Option<String>,
254 p_min: Option<String>,
256 value: Option<i64>,
258 },
259 Float {
262 name: String,
263 addressing: Option<Addressing>,
265 access: AccessMode,
266 min: f64,
267 max: f64,
268 unit: Option<String>,
269 scale: Option<(i64, i64)>,
271 offset: Option<f64>,
273 selectors: Vec<String>,
274 selected_if: Vec<(String, Vec<String>)>,
275 pvalue: Option<String>,
277 },
278 Enum {
280 name: String,
281 addressing: Option<Addressing>,
283 access: AccessMode,
284 entries: Vec<EnumEntryDecl>,
285 default: Option<String>,
286 selectors: Vec<String>,
287 selected_if: Vec<(String, Vec<String>)>,
288 pvalue: Option<String>,
290 },
291 Boolean {
293 name: String,
294 addressing: Option<Addressing>,
296 len: u32,
297 access: AccessMode,
298 bitfield: Option<BitField>,
299 selectors: Vec<String>,
300 selected_if: Vec<(String, Vec<String>)>,
301 pvalue: Option<String>,
303 on_value: Option<i64>,
305 off_value: Option<i64>,
307 },
308 Command {
310 name: String,
311 address: Option<u64>,
313 len: u32,
314 pvalue: Option<String>,
316 command_value: Option<i64>,
318 },
319 Category { name: String, children: Vec<String> },
321 SwissKnife(SwissKnifeDecl),
323 Converter(ConverterDecl),
325 IntConverter(IntConverterDecl),
327 String(StringDecl),
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct XmlModel {
334 pub version: String,
336 pub nodes: Vec<NodeDecl>,
338}
339
340#[derive(Debug, Clone, PartialEq, Eq)]
342pub struct MinimalXmlInfo {
343 pub schema_version: Option<String>,
344 pub top_level_features: Vec<String>,
345}
346
347pub fn parse_into_minimal_nodes(xml: &str) -> Result<MinimalXmlInfo, XmlError> {
349 let mut reader = Reader::from_str(xml);
350 reader.trim_text(true);
351 let mut buf = Vec::new();
352 let mut depth = 0usize;
353 let mut schema_version: Option<String> = None;
354 let mut top_level_features = Vec::new();
355
356 loop {
357 match reader.read_event_into(&mut buf) {
358 Ok(Event::Start(e)) => {
359 depth += 1;
360 handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
361 }
362 Ok(Event::Empty(e)) => {
363 depth += 1;
364 handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
365 if depth > 0 {
366 depth = depth.saturating_sub(1);
367 }
368 }
369 Ok(Event::End(_)) => {
370 if depth > 0 {
371 depth = depth.saturating_sub(1);
372 }
373 }
374 Ok(Event::Eof) => break,
375 Err(err) => return Err(XmlError::Xml(err.to_string())),
376 _ => {}
377 }
378 buf.clear();
379 }
380
381 Ok(MinimalXmlInfo {
382 schema_version,
383 top_level_features,
384 })
385}
386
387pub fn parse(xml: &str) -> Result<XmlModel, XmlError> {
393 let mut reader = Reader::from_str(xml);
394 reader.trim_text(true);
395 let mut buf = Vec::new();
396 let mut version = String::from("0.0.0");
397 let mut nodes = Vec::new();
398
399 loop {
400 match reader.read_event_into(&mut buf) {
401 Ok(Event::Start(ref e)) => match e.name().as_ref() {
402 b"RegisterDescription" => {
403 version = schema_version_from(e)?;
404 }
405 b"Integer" | b"IntReg" | b"MaskedIntReg" => {
406 let node = parse_integer(&mut reader, e.clone())?;
407 nodes.push(node);
408 }
409 b"IntSwissKnife" => {
410 let node = parse_swissknife(&mut reader, e.clone())?;
411 nodes.push(node);
412 }
413 b"Float" | b"FloatReg" => {
414 let node = parse_float(&mut reader, e.clone())?;
415 nodes.push(node);
416 }
417 b"Enumeration" => {
418 let node = parse_enum(&mut reader, e.clone())?;
419 nodes.push(node);
420 }
421 b"Boolean" => {
422 let node = parse_boolean(&mut reader, e.clone())?;
423 nodes.push(node);
424 }
425 b"Command" => {
426 let node = parse_command(&mut reader, e.clone())?;
427 nodes.push(node);
428 }
429 b"Category" => {
430 let node = parse_category(&mut reader, e.clone())?;
431 nodes.push(node);
432 }
433 b"SwissKnife" => {
434 let node = parse_swissknife(&mut reader, e.clone())?;
435 nodes.push(node);
436 }
437 b"Converter" => {
438 let node = parse_converter(&mut reader, e.clone())?;
439 nodes.push(node);
440 }
441 b"IntConverter" => {
442 let node = parse_int_converter(&mut reader, e.clone())?;
443 nodes.push(node);
444 }
445 b"StringReg" | b"String" => {
446 let node = parse_string(&mut reader, e.clone())?;
447 nodes.push(node);
448 }
449 b"StructReg" => {
450 let entries = parse_struct_reg(&mut reader, e.clone())?;
451 nodes.extend(entries);
452 }
453 b"Port" => {
454 skip_element(&mut reader, e.name().as_ref())?;
456 }
457 _ => {
458 skip_element(&mut reader, e.name().as_ref())?;
459 }
460 },
461 Ok(Event::Empty(ref e)) => match e.name().as_ref() {
462 b"RegisterDescription" => {
463 version = schema_version_from(e)?;
464 }
465 b"Command" => {
466 let node = parse_command_empty(e)?;
467 nodes.push(node);
468 }
469 b"Category" => {
470 let node = parse_category_empty(e)?;
471 nodes.push(node);
472 }
473 _ => {}
474 },
475 Ok(Event::Eof) => break,
476 Err(err) => return Err(XmlError::Xml(err.to_string())),
477 _ => {}
478 }
479 buf.clear();
480 }
481
482 Ok(XmlModel { version, nodes })
483}
484
485fn schema_version_from(event: &BytesStart<'_>) -> Result<String, XmlError> {
486 let major = attribute_value(event, b"SchemaMajorVersion")?;
487 let minor = attribute_value(event, b"SchemaMinorVersion")?;
488 let sub = attribute_value(event, b"SchemaSubMinorVersion")?;
489 let major = major.unwrap_or_else(|| "0".to_string());
490 let minor = minor.unwrap_or_else(|| "0".to_string());
491 let sub = sub.unwrap_or_else(|| "0".to_string());
492 Ok(format!("{major}.{minor}.{sub}"))
493}
494
495fn handle_start(
496 event: &BytesStart<'_>,
497 depth: usize,
498 schema_version: &mut Option<String>,
499 top_level: &mut Vec<String>,
500) -> Result<(), XmlError> {
501 if depth == 1 && schema_version.is_none() {
502 *schema_version = extract_schema_version(event);
503 } else if depth == 2 {
504 if let Some(name) = attribute_value(event, b"Name")? {
505 top_level.push(name);
506 } else {
507 top_level.push(String::from_utf8_lossy(event.name().as_ref()).to_string());
508 }
509 }
510 Ok(())
511}
512
513fn extract_schema_version(event: &BytesStart<'_>) -> Option<String> {
514 let major = attribute_value(event, b"SchemaMajorVersion").ok().flatten();
515 let minor = attribute_value(event, b"SchemaMinorVersion").ok().flatten();
516 let sub = attribute_value(event, b"SchemaSubMinorVersion")
517 .ok()
518 .flatten();
519 if major.is_none() && minor.is_none() && sub.is_none() {
520 None
521 } else {
522 let major = major.unwrap_or_else(|| "0".to_string());
523 let minor = minor.unwrap_or_else(|| "0".to_string());
524 let sub = sub.unwrap_or_else(|| "0".to_string());
525 Some(format!("{major}.{minor}.{sub}"))
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532
533 const FIXTURE: &str = r#"
534 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="2" SchemaSubMinorVersion="3">
535 <Category Name="Root">
536 <pFeature>Gain</pFeature>
537 <pFeature>GainSelector</pFeature>
538 </Category>
539 <Integer Name="Width">
540 <Address>0x0000_0100</Address>
541 <Length>4</Length>
542 <AccessMode>RW</AccessMode>
543 <Min>16</Min>
544 <Max>4096</Max>
545 <Inc>2</Inc>
546 </Integer>
547 <Float Name="ExposureTime">
548 <Address>0x0000_0200</Address>
549 <Length>4</Length>
550 <AccessMode>RW</AccessMode>
551 <Min>10.0</Min>
552 <Max>200000.0</Max>
553 <Scale>1/1000</Scale>
554 <Offset>0.0</Offset>
555 </Float>
556 <Enumeration Name="GainSelector">
557 <Address>0x0000_0300</Address>
558 <Length>2</Length>
559 <AccessMode>RW</AccessMode>
560 <EnumEntry Name="AnalogAll" Value="0" />
561 <EnumEntry Name="DigitalAll" Value="1" />
562 </Enumeration>
563 <Integer Name="Gain">
564 <Address>0x0000_0304</Address>
565 <Length>2</Length>
566 <AccessMode>RW</AccessMode>
567 <Min>0</Min>
568 <Max>48</Max>
569 <pSelected>GainSelector</pSelected>
570 <Selected>AnalogAll</Selected>
571 </Integer>
572 <Boolean Name="GammaEnable">
573 <Address>0x0000_0400</Address>
574 <Length>1</Length>
575 <AccessMode>RW</AccessMode>
576 </Boolean>
577 <Command Name="AcquisitionStart">
578 <Address>0x0000_0500</Address>
579 <Length>4</Length>
580 </Command>
581 </RegisterDescription>
582 "#;
583
584 #[test]
585 fn parse_minimal_xml() {
586 let info = parse_into_minimal_nodes(FIXTURE).expect("parse xml");
587 assert_eq!(info.schema_version.as_deref(), Some("1.2.3"));
588 assert_eq!(info.top_level_features.len(), 7);
589 assert_eq!(info.top_level_features[0], "Root");
590 }
591
592 #[test]
593 fn parse_fixture_model() {
594 let model = parse(FIXTURE).expect("parse fixture");
595 assert_eq!(model.version, "1.2.3");
596 assert_eq!(model.nodes.len(), 7);
597 match &model.nodes[0] {
598 NodeDecl::Category { name, children } => {
599 assert_eq!(name, "Root");
600 assert_eq!(
601 children,
602 &vec!["Gain".to_string(), "GainSelector".to_string()]
603 );
604 }
605 other => panic!("unexpected node: {other:?}"),
606 }
607 match &model.nodes[1] {
608 NodeDecl::Integer {
609 name,
610 min,
611 max,
612 inc,
613 ..
614 } => {
615 assert_eq!(name, "Width");
616 assert_eq!(*min, 16);
617 assert_eq!(*max, 4096);
618 assert_eq!(*inc, Some(2));
619 }
620 other => panic!("unexpected node: {other:?}"),
621 }
622 match &model.nodes[2] {
623 NodeDecl::Float {
624 name,
625 scale,
626 offset,
627 ..
628 } => {
629 assert_eq!(name, "ExposureTime");
630 assert_eq!(*scale, Some((1, 1000)));
631 assert_eq!(*offset, Some(0.0));
632 }
633 other => panic!("unexpected node: {other:?}"),
634 }
635 match &model.nodes[3] {
636 NodeDecl::Enum { name, entries, .. } => {
637 assert_eq!(name, "GainSelector");
638 assert_eq!(entries.len(), 2);
639 assert!(matches!(entries[0].value, EnumValueSrc::Literal(0)));
640 assert!(matches!(entries[1].value, EnumValueSrc::Literal(1)));
641 }
642 other => panic!("unexpected node: {other:?}"),
643 }
644 match &model.nodes[4] {
645 NodeDecl::Integer {
646 name, selected_if, ..
647 } => {
648 assert_eq!(name, "Gain");
649 assert_eq!(selected_if.len(), 1);
650 assert_eq!(selected_if[0].0, "GainSelector");
651 assert_eq!(selected_if[0].1, vec!["AnalogAll".to_string()]);
652 }
653 other => panic!("unexpected node: {other:?}"),
654 }
655 }
656
657 #[test]
658 fn parse_swissknife_node() {
659 const XML: &str = r#"
660 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
661 <Integer Name="GainRaw">
662 <Address>0x3000</Address>
663 <Length>4</Length>
664 <AccessMode>RW</AccessMode>
665 <Min>0</Min>
666 <Max>1000</Max>
667 </Integer>
668 <Float Name="Offset">
669 <Address>0x3008</Address>
670 <Length>4</Length>
671 <AccessMode>RW</AccessMode>
672 <Min>-100.0</Min>
673 <Max>100.0</Max>
674 </Float>
675 <SwissKnife Name="ComputedGain">
676 <Expression>(GainRaw * 0.5) + Offset</Expression>
677 <pVariable Name="GainRaw">GainRaw</pVariable>
678 <pVariable Name="Offset">Offset</pVariable>
679 <Output>Float</Output>
680 </SwissKnife>
681 </RegisterDescription>
682 "#;
683
684 let model = parse(XML).expect("parse swissknife xml");
685 assert_eq!(model.nodes.len(), 3);
686 let swiss = model
687 .nodes
688 .iter()
689 .find_map(|decl| match decl {
690 NodeDecl::SwissKnife(node) => Some(node),
691 _ => None,
692 })
693 .expect("swissknife present");
694 assert_eq!(swiss.name, "ComputedGain");
695 assert_eq!(swiss.expr, "(GainRaw * 0.5) + Offset");
696 assert_eq!(swiss.output, SkOutput::Float);
697 assert_eq!(swiss.variables.len(), 2);
698 assert_eq!(
699 swiss.variables[0],
700 ("GainRaw".to_string(), "GainRaw".to_string())
701 );
702 assert_eq!(
703 swiss.variables[1],
704 ("Offset".to_string(), "Offset".to_string())
705 );
706 }
707
708 #[test]
709 fn parse_int_swissknife_with_hex_and_ampersand() {
710 const XML: &str = r#"
712 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
713 <IntSwissKnife Name="PayloadSize">
714 <pVariable Name="W">Width</pVariable>
715 <pVariable Name="H">Height</pVariable>
716 <pVariable Name="PF">PixelFormat</pVariable>
717 <Formula>W * H * ((PF>>16)&0xFF) / 8</Formula>
718 </IntSwissKnife>
719 </RegisterDescription>
720 "#;
721
722 let model = parse(XML).expect("parse intswissknife");
723 assert_eq!(model.nodes.len(), 1);
724 let swiss = model
725 .nodes
726 .iter()
727 .find_map(|decl| match decl {
728 NodeDecl::SwissKnife(node) => Some(node),
729 _ => None,
730 })
731 .expect("swissknife present");
732 assert_eq!(swiss.name, "PayloadSize");
733 assert!(
735 swiss.expr.contains('&'),
736 "expression should contain decoded '&': {}",
737 swiss.expr
738 );
739 assert!(
740 swiss.expr.contains("0xFF"),
741 "expression should contain hex literal: {}",
742 swiss.expr
743 );
744 }
745
746 #[test]
747 fn parse_enum_entry_with_pvalue() {
748 const XML: &str = r#"
749 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
750 <Enumeration Name="Mode">
751 <Address>0x0000_4000</Address>
752 <Length>4</Length>
753 <AccessMode>RW</AccessMode>
754 <EnumEntry Name="Fixed10">
755 <Value>10</Value>
756 </EnumEntry>
757 <EnumEntry Name="DynFromReg">
758 <pValue>RegModeVal</pValue>
759 </EnumEntry>
760 </Enumeration>
761 <Integer Name="RegModeVal">
762 <Address>0x0000_4100</Address>
763 <Length>4</Length>
764 <AccessMode>RW</AccessMode>
765 <Min>0</Min>
766 <Max>65535</Max>
767 </Integer>
768 </RegisterDescription>
769 "#;
770
771 let model = parse(XML).expect("parse enum pvalue");
772 assert_eq!(model.nodes.len(), 2);
773 match &model.nodes[0] {
774 NodeDecl::Enum { entries, .. } => {
775 assert_eq!(entries.len(), 2);
776 assert!(matches!(entries[0].value, EnumValueSrc::Literal(10)));
777 match &entries[1].value {
778 EnumValueSrc::FromNode(node) => assert_eq!(node, "RegModeVal"),
779 other => panic!("unexpected entry value: {other:?}"),
780 }
781 }
782 other => panic!("unexpected node: {other:?}"),
783 }
784 }
785
786 #[test]
787 fn parse_indirect_addressing() {
788 const XML: &str = r#"
789 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
790 <Integer Name="RegAddr">
791 <Address>0x2000</Address>
792 <Length>4</Length>
793 <AccessMode>RW</AccessMode>
794 <Min>0</Min>
795 <Max>65535</Max>
796 </Integer>
797 <Integer Name="Gain" Address="0xFFFF">
798 <pAddress>RegAddr</pAddress>
799 <Length>4</Length>
800 <AccessMode>RW</AccessMode>
801 <Min>0</Min>
802 <Max>255</Max>
803 </Integer>
804 </RegisterDescription>
805 "#;
806
807 let model = parse(XML).expect("parse indirect xml");
808 assert_eq!(model.nodes.len(), 2);
809 match &model.nodes[0] {
810 NodeDecl::Integer {
811 name, addressing, ..
812 } => {
813 assert_eq!(name, "RegAddr");
814 assert!(
815 matches!(addressing, Some(Addressing::Fixed { address, len }) if *address == 0x2000 && *len == 4)
816 );
817 }
818 other => panic!("unexpected node: {other:?}"),
819 }
820 match &model.nodes[1] {
821 NodeDecl::Integer {
822 name, addressing, ..
823 } => {
824 assert_eq!(name, "Gain");
825 match addressing {
826 Some(Addressing::Indirect {
827 p_address_node,
828 len,
829 }) => {
830 assert_eq!(p_address_node, "RegAddr");
831 assert_eq!(*len, 4);
832 }
833 other => panic!("expected indirect addressing, got {other:?}"),
834 }
835 }
836 other => panic!("unexpected node: {other:?}"),
837 }
838 }
839
840 #[test]
841 fn parse_integer_bitfield_big_endian() {
842 const XML: &str = r#"
843 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
844 <Integer Name="Packed">
845 <Address>0x1000</Address>
846 <Length>4</Length>
847 <AccessMode>RW</AccessMode>
848 <Min>0</Min>
849 <Max>65535</Max>
850 <Lsb>8</Lsb>
851 <Msb>15</Msb>
852 <Endianness>BigEndian</Endianness>
853 </Integer>
854 </RegisterDescription>
855 "#;
856
857 let model = parse(XML).expect("parse big-endian bitfield");
858 assert_eq!(model.nodes.len(), 1);
859 match &model.nodes[0] {
860 NodeDecl::Integer { len, bitfield, .. } => {
861 assert_eq!(*len, 4);
862 let field = bitfield.as_ref().expect("bitfield present");
863 assert_eq!(field.byte_order, ByteOrder::Big);
864 assert_eq!(field.bit_length, 8);
865 assert_eq!(field.bit_offset, 16);
866 }
867 other => panic!("unexpected node: {other:?}"),
868 }
869 }
870
871 #[test]
872 fn parse_boolean_bitfield_default_length() {
873 const XML: &str = r#"
874 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
875 <Boolean Name="Flag">
876 <Address>0x2000</Address>
877 <Length>1</Length>
878 <AccessMode>RW</AccessMode>
879 <Bit>3</Bit>
880 </Boolean>
881 </RegisterDescription>
882 "#;
883
884 let model = parse(XML).expect("parse boolean bitfield");
885 assert_eq!(model.nodes.len(), 1);
886 match &model.nodes[0] {
887 NodeDecl::Boolean { len, bitfield, .. } => {
888 assert_eq!(*len, 1);
889 let bf = bitfield.as_ref().expect("bitfield present");
890 assert_eq!(bf.byte_order, ByteOrder::Little);
891 assert_eq!(bf.bit_length, 1);
892 assert_eq!(bf.bit_offset, 3);
893 }
894 other => panic!("unexpected node: {other:?}"),
895 }
896 }
897
898 #[test]
899 fn parse_integer_bitfield_mask() {
900 const XML: &str = r#"
901 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
902 <Integer Name="Masked">
903 <Address>0x3000</Address>
904 <Length>4</Length>
905 <AccessMode>RW</AccessMode>
906 <Min>0</Min>
907 <Max>65535</Max>
908 <Mask>0x0000FF00</Mask>
909 </Integer>
910 </RegisterDescription>
911 "#;
912
913 let model = parse(XML).expect("parse mask bitfield");
914 assert_eq!(model.nodes.len(), 1);
915 match &model.nodes[0] {
916 NodeDecl::Integer { bitfield, .. } => {
917 let field = bitfield.as_ref().expect("bitfield present");
918 assert_eq!(field.byte_order, ByteOrder::Little);
919 assert_eq!(field.bit_length, 8);
920 assert_eq!(field.bit_offset, 8);
921 }
922 other => panic!("unexpected node: {other:?}"),
923 }
924 }
925}