diff --git a/generation/Cargo.toml b/generation/Cargo.toml index 65e82643..b8d140a8 100644 --- a/generation/Cargo.toml +++ b/generation/Cargo.toml @@ -12,6 +12,7 @@ description = "Code generation for the `device-driver` crate" readme.workspace = true [dependencies] +anyhow = "1.0.86" convert_case = "0.6.0" indexmap = { version = "2.1.0", features = ["serde"] } prettyplease = "0.2.15" diff --git a/generation/src/dsl_hir_mir_transform.rs b/generation/src/dsl_hir/mir_transform.rs similarity index 98% rename from generation/src/dsl_hir_mir_transform.rs rename to generation/src/dsl_hir/mir_transform.rs index 9e53d101..2848ff2e 100644 --- a/generation/src/dsl_hir_mir_transform.rs +++ b/generation/src/dsl_hir/mir_transform.rs @@ -63,20 +63,6 @@ impl TryFrom for mir::Integer { } } -impl From for mir::NameCase { - fn from(value: dsl_hir::NameCase) -> Self { - match value { - dsl_hir::NameCase::Varying => mir::NameCase::Varying, - dsl_hir::NameCase::Pascal => mir::NameCase::Pascal, - dsl_hir::NameCase::Snake => mir::NameCase::Snake, - dsl_hir::NameCase::ScreamingSnake => mir::NameCase::ScreamingSnake, - dsl_hir::NameCase::Camel => mir::NameCase::Camel, - dsl_hir::NameCase::Kebab => mir::NameCase::Kebab, - dsl_hir::NameCase::Cobol => mir::NameCase::Cobol, - } - } -} - impl TryFrom for mir::Repeat { type Error = syn::Error; @@ -145,7 +131,7 @@ impl TryFrom for mir::GlobalConfig { dsl_hir::GlobalConfig::BufferAddressType(value) => { global_config.buffer_address_type = Some(value.try_into()?) } - dsl_hir::GlobalConfig::NameCase(value) => global_config.name_case = value.into(), + dsl_hir::GlobalConfig::NameWordBoundaries(value) => global_config.name_word_boundaries = value, } } @@ -856,6 +842,8 @@ fn transform_command_override( #[cfg(test)] mod tests { + use convert_case::Boundary; + use super::*; #[test] @@ -883,7 +871,7 @@ mod tests { type RegisterAddressType = i8; type CommandAddressType = u128; type BufferAddressType = u32; - type NameCase = Pascal; + type NameWordBoundaries = \"-\"; }", ) .unwrap(); @@ -901,7 +889,7 @@ mod tests { register_address_type: Some(mir::Integer::I8), command_address_type: Some(mir::Integer::U128), buffer_address_type: Some(mir::Integer::U32), - name_case: mir::NameCase::Pascal, + name_word_boundaries: vec![Boundary::Hyphen], } ); } diff --git a/generation/src/dsl_hir.rs b/generation/src/dsl_hir/mod.rs similarity index 96% rename from generation/src/dsl_hir.rs rename to generation/src/dsl_hir/mod.rs index 7a981a23..2da9751a 100644 --- a/generation/src/dsl_hir.rs +++ b/generation/src/dsl_hir/mod.rs @@ -26,11 +26,12 @@ //! > | (`type` `RegisterAddressType` `=` _IntegerType_`;`) //! > | (`type` `CommandAddressType` `=` _IntegerType_`;`) //! > | (`type` `BufferAddressType` `=` _IntegerType_`;`) -//! > | (`type` `NameCase` `=` _NameCase_`;`) +//! > | (`type` `NameWordBoundaries` `=` _NameWordBoundaries_`;`) //! -//! _NameCase_: +//! _NameWordBoundaries_: //! This specifies the input, not the output. Only applies to object and field names. -//! > `Varying`|`Pascal`|`Snake`|`ScreamingSnake`|`Camel`|`Kebab`|`Cobol` +//! > [_Boundary_*] +//! > | _String_ //! //! _ObjectList_: //! > (_Object_(`,` _Object_)*`,`?)? @@ -150,15 +151,18 @@ use std::mem::Discriminant; +use convert_case::Boundary; use proc_macro2::Span; use syn::{ braced, bracketed, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, - LitInt, Token, + Ident, LitInt, LitStr, Token, }; +pub mod mir_transform; + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Device { pub global_config_list: GlobalConfigList, @@ -211,7 +215,7 @@ pub enum GlobalConfig { RegisterAddressType(syn::Ident), CommandAddressType(syn::Ident), BufferAddressType(syn::Ident), - NameCase(NameCase), + NameWordBoundaries(Vec), } impl Parse for GlobalConfig { @@ -268,54 +272,42 @@ impl Parse for GlobalConfig { let value = input.parse()?; input.parse::()?; Ok(Self::BufferAddressType(value)) - } else if lookahead.peek(kw::NameCase) { - input.parse::()?; + } else if lookahead.peek(kw::NameWordBoundaries) { + input.parse::()?; input.parse::()?; - let value = input.parse()?; - input.parse::()?; - Ok(Self::NameCase(value)) - } else { - Err(lookahead.error()) - } - } -} -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum NameCase { - Varying, - Pascal, - Snake, - ScreamingSnake, - Camel, - Kebab, - Cobol, -} + let value = if input.peek(syn::token::Bracket) { + let bracket_input; + bracketed!(bracket_input in input); + + let boundaries = Punctuated::::parse_terminated(&bracket_input)?; + boundaries + .into_iter() + .map(|ident| { + for b in Boundary::all() { + if format!("{b:?}").to_lowercase() == ident.to_string().to_lowercase() { + return Ok(b); + } + } -impl Parse for NameCase { - fn parse(input: ParseStream) -> syn::Result { - let lookahead = input.lookahead1(); + Err(syn::Error::new(ident.span(), &format!("`{}` is not a valid boundary name. One of the following was expected: {:?}", ident, Boundary::all()))) + }).collect::, _>>()? + } else { + let string_value = match input.parse::() { + Ok(lit) => lit.value(), + Err(e) => { + return Err(syn::Error::new( + e.span(), + "Expected an array of boundaries or a string", + )); + } + }; + + Boundary::list_from(&string_value) + }; - if lookahead.peek(kw::Varying) { - input.parse::()?; - Ok(Self::Varying) - } else if lookahead.peek(kw::Pascal) { - input.parse::()?; - Ok(Self::Pascal) - } else if lookahead.peek(kw::Snake) { - input.parse::()?; - Ok(Self::Snake) - } else if lookahead.peek(kw::ScreamingSnake) { - input.parse::()?; - Ok(Self::ScreamingSnake) - } else if lookahead.peek(kw::Camel) { - input.parse::()?; - Ok(Self::Camel) - } else if lookahead.peek(kw::Kebab) { - input.parse::()?; - Ok(Self::Kebab) - } else if lookahead.peek(kw::Cobol) { - input.parse::()?; - Ok(Self::Cobol) + input.parse::()?; + Ok(Self::NameWordBoundaries(value)) } else { Err(lookahead.error()) } @@ -1394,16 +1386,7 @@ mod kw { syn::custom_keyword!(RegisterAddressType); syn::custom_keyword!(CommandAddressType); syn::custom_keyword!(BufferAddressType); - syn::custom_keyword!(NameCase); - - // NameCase options - syn::custom_keyword!(Varying); - syn::custom_keyword!(Pascal); - syn::custom_keyword!(Snake); - syn::custom_keyword!(ScreamingSnake); - syn::custom_keyword!(Camel); - syn::custom_keyword!(Kebab); - syn::custom_keyword!(Cobol); + syn::custom_keyword!(NameWordBoundaries); // Access syn::custom_keyword!(Access); @@ -2200,42 +2183,6 @@ mod tests { ); } - #[test] - fn parse_name_case() { - assert_eq!( - syn::parse_str::("Varying").unwrap(), - NameCase::Varying - ); - assert_eq!( - syn::parse_str::("Pascal").unwrap(), - NameCase::Pascal - ); - assert_eq!( - syn::parse_str::("Snake").unwrap(), - NameCase::Snake - ); - assert_eq!( - syn::parse_str::("ScreamingSnake").unwrap(), - NameCase::ScreamingSnake - ); - assert_eq!( - syn::parse_str::("Camel").unwrap(), - NameCase::Camel - ); - assert_eq!( - syn::parse_str::("Kebab").unwrap(), - NameCase::Kebab - ); - assert_eq!( - syn::parse_str::("Cobol").unwrap(), - NameCase::Cobol - ); - assert_eq!( - syn::parse_str::("bla").unwrap_err().to_string(), - "expected one of: `Varying`, `Pascal`, `Snake`, `ScreamingSnake`, `Camel`, `Kebab`, `Cobol`" - ); - } - #[test] fn parse_global_config_list() { assert_eq!( @@ -2278,18 +2225,40 @@ mod tests { assert_eq!( syn::parse_str::( - "config { type DefaultByteOrder = LE; type DefaultBitOrder = LSB0; type NameCase = Camel; }" + "config { type DefaultByteOrder = LE; type DefaultBitOrder = LSB0; type NameWordBoundaries = \"aA:1B\"; }" ) .unwrap(), GlobalConfigList { configs: vec![ GlobalConfig::DefaultByteOrder(ByteOrder::LE), GlobalConfig::DefaultBitOrder(BitOrder::LSB0), - GlobalConfig::NameCase(NameCase::Camel) + GlobalConfig::NameWordBoundaries(vec![Boundary::LowerUpper, Boundary::DigitUpper]) ] } ); + assert_eq!( + syn::parse_str::("config { type NameWordBoundaries = [DigitLower, Hyphen]; }") + .unwrap(), + GlobalConfigList { + configs: vec![ + GlobalConfig::NameWordBoundaries(vec![Boundary::DigitLower, Boundary::Hyphen]) + ] + } + ); + + assert_eq!( + syn::parse_str::("config { type NameWordBoundaries = 5; }") + .unwrap_err().to_string(), + "Expected an array of boundaries or a string" + ); + + assert_eq!( + syn::parse_str::("config { type NameWordBoundaries = [lol]; }") + .unwrap_err().to_string(), + "`lol` is not a valid boundary name. One of the following was expected: [Hyphen, Underscore, Space, LowerUpper, UpperLower, DigitUpper, UpperDigit, DigitLower, LowerDigit, Acronym]" + ); + assert_eq!( syn::parse_str::( "config { type RegisterAddressType = u8; type CommandAddressType = u16; type BufferAddressType = u32; }" @@ -2308,7 +2277,7 @@ mod tests { syn::parse_str::("config { type DefaultRegisterAccesssss = RW; }") .unwrap_err() .to_string(), - "expected one of: `DefaultRegisterAccess`, `DefaultFieldAccess`, `DefaultBufferAccess`, `DefaultByteOrder`, `DefaultBitOrder`, `RegisterAddressType`, `CommandAddressType`, `BufferAddressType`, `NameCase`" + "expected one of: `DefaultRegisterAccess`, `DefaultFieldAccess`, `DefaultBufferAccess`, `DefaultByteOrder`, `DefaultBitOrder`, `RegisterAddressType`, `CommandAddressType`, `BufferAddressType`, `NameWordBoundaries`" ); } diff --git a/generation/src/lib.rs b/generation/src/lib.rs index b454aec3..1a208da3 100644 --- a/generation/src/lib.rs +++ b/generation/src/lib.rs @@ -14,7 +14,6 @@ pub mod deserialization; mod generation; pub mod dsl_hir; -pub mod dsl_hir_mir_transform; pub mod mir; #[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] diff --git a/generation/src/mir.rs b/generation/src/mir/mod.rs similarity index 59% rename from generation/src/mir.rs rename to generation/src/mir/mod.rs index a8a664ac..bc783747 100644 --- a/generation/src/mir.rs +++ b/generation/src/mir/mod.rs @@ -3,13 +3,17 @@ use std::ops::Range; +use convert_case::Boundary; + +pub mod passes; + #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Device { pub global_config: GlobalConfig, pub objects: Vec, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct GlobalConfig { pub default_register_access: Access, pub default_field_access: Access, @@ -19,7 +23,23 @@ pub struct GlobalConfig { pub register_address_type: Option, pub command_address_type: Option, pub buffer_address_type: Option, - pub name_case: NameCase, + pub name_word_boundaries: Vec, +} + +impl Default for GlobalConfig { + fn default() -> Self { + Self { + default_register_access: Default::default(), + default_field_access: Default::default(), + default_buffer_access: Default::default(), + default_byte_order: Default::default(), + default_bit_order: Default::default(), + register_address_type: Default::default(), + command_address_type: Default::default(), + buffer_address_type: Default::default(), + name_word_boundaries: convert_case::Boundary::defaults(), + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -60,18 +80,6 @@ pub enum BitOrder { MSB0, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum NameCase { - #[default] - Varying, - Pascal, - Snake, - ScreamingSnake, - Camel, - Kebab, - Cobol, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum Object { Block(Block), @@ -81,6 +89,63 @@ pub enum Object { Ref(RefObject), } +impl Object { + pub(self) fn get_block_object_list(&mut self) -> Option<&mut Vec> { + match self { + Object::Block(b) => Some(&mut b.objects), + _ => None, + } + } + + /// Get a mutable reference to the name of the specific object + pub(self) fn name_mut(&mut self) -> &mut String { + match self { + Object::Block(val) => &mut val.name, + Object::Register(val) => &mut val.name, + Object::Command(val) => &mut val.name, + Object::Buffer(val) => &mut val.name, + Object::Ref(val) => &mut val.name, + } + } + + /// Get a reference to the name of the specific object + pub(self) fn name(&self) -> &String { + match self { + Object::Block(val) => &val.name, + Object::Register(val) => &val.name, + Object::Command(val) => &val.name, + Object::Buffer(val) => &val.name, + Object::Ref(val) => &val.name, + } + } + + /// If the object has fields, get an iterator over all of them + pub(self) fn fields_mut(&mut self) -> Option + '_>> { + match self { + Object::Block(_) => None, + Object::Register(val) => Some(Box::new(val.fields.iter_mut())), + Object::Command(val) => Some(Box::new( + val.in_fields.iter_mut().chain(val.out_fields.iter_mut()), + )), + Object::Buffer(_) => None, + Object::Ref(_) => None, + } + } + + /// If the object has fields, get an iterator over all of them + pub(self) fn fields(&self) -> Option + '_>> { + match self { + Object::Block(_) => None, + Object::Register(val) => Some(Box::new(val.fields.iter())), + Object::Command(val) => { + Some(Box::new(val.in_fields.iter().chain(val.out_fields.iter()))) + } + Object::Buffer(_) => None, + Object::Ref(_) => None, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Block { pub cfg_attr: Option, @@ -97,7 +162,7 @@ pub struct Repeat { pub stride: i64, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Register { pub cfg_attr: Option, pub description: String, @@ -112,7 +177,7 @@ pub struct Register { pub fields: Vec, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Field { pub cfg_attr: Option, pub description: String, @@ -123,9 +188,10 @@ pub struct Field { pub field_address: Range, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum BaseType { Bool, + #[default] Uint, Int, } @@ -139,7 +205,7 @@ pub enum FieldConversion { }, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct EnumVariant { pub cfg_attr: Option, pub description: String, @@ -147,8 +213,9 @@ pub struct EnumVariant { pub value: EnumValue, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum EnumValue { + #[default] Unspecified, Specified(i128), Default, @@ -170,7 +237,7 @@ pub struct Command { pub out_fields: Vec, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Buffer { pub cfg_attr: Option, pub description: String, diff --git a/generation/src/mir/passes/enum_values_specified.rs b/generation/src/mir/passes/enum_values_specified.rs new file mode 100644 index 00000000..4b2ff4a4 --- /dev/null +++ b/generation/src/mir/passes/enum_values_specified.rs @@ -0,0 +1,5 @@ +use crate::mir::Device; + +pub fn run_pass(device: &mut Device) -> anyhow::Result<()> { + todo!() +} diff --git a/generation/src/mir/passes/mod.rs b/generation/src/mir/passes/mod.rs new file mode 100644 index 00000000..fd2da1e9 --- /dev/null +++ b/generation/src/mir/passes/mod.rs @@ -0,0 +1,28 @@ +use super::{Device, Object}; + +mod enum_values_specified; +mod names_normalized; +mod names_unique; + +pub fn run_passes(device: &mut Device) -> anyhow::Result<()> { + names_normalized::run_pass(device)?; + names_unique::run_pass(device)?; + // enum_values_specified::run_pass(device)?; + + Ok(()) +} + +pub(self) fn recurse_objects( + objects: &mut Vec, + f: &mut impl FnMut(&mut Object) -> anyhow::Result<()>, +) -> anyhow::Result<()> { + for object in objects.iter_mut() { + f(object)?; + + if let Some(objects) = object.get_block_object_list() { + recurse_objects(objects, f)?; + } + } + + Ok(()) +} diff --git a/generation/src/mir/passes/names_normalized.rs b/generation/src/mir/passes/names_normalized.rs new file mode 100644 index 00000000..c13deb2b --- /dev/null +++ b/generation/src/mir/passes/names_normalized.rs @@ -0,0 +1,127 @@ +use convert_case::Case; + +use crate::mir::{Device, FieldConversion}; + +use super::recurse_objects; + +pub fn run_pass(device: &mut Device) -> anyhow::Result<()> { + let boundaries = device.global_config.name_word_boundaries.clone(); + + let pascal_converter = convert_case::Converter::new() + .set_boundaries(&boundaries) + .to_case(Case::Pascal); + let snake_converter = convert_case::Converter::new() + .set_boundaries(&boundaries) + .to_case(Case::Snake); + + recurse_objects(&mut device.objects, &mut |object| { + *object.name_mut() = pascal_converter.convert(object.name_mut()); + + for field in object + .fields_mut() + .unwrap_or_else(|| Box::new(std::iter::empty())) + { + field.name = snake_converter.convert(&field.name); + if let Some(FieldConversion::Enum { name, variants }) = field.field_conversion.as_mut() + { + *name = pascal_converter.convert(&*name); + + for v in variants.iter_mut() { + v.name = pascal_converter.convert(&v.name) + } + } + } + + Ok(()) + }) +} + +#[cfg(test)] +mod tests { + use convert_case::Boundary; + + use crate::mir::{Buffer, EnumVariant, Field, GlobalConfig, Object, Register}; + + use super::*; + + #[test] + fn names_normalized() { + let global_config = GlobalConfig { + default_register_access: Default::default(), + default_field_access: Default::default(), + default_buffer_access: Default::default(), + default_byte_order: Default::default(), + default_bit_order: Default::default(), + register_address_type: Default::default(), + command_address_type: Default::default(), + buffer_address_type: Default::default(), + name_word_boundaries: Boundary::list_from("-"), + }; + + let mut start_mir = Device { + global_config: global_config.clone(), + objects: vec![ + Object::Register(Register { + name: "my-reGister".into(), + fields: vec![ + Field { + name: "my-fielD".into(), + ..Default::default() + }, + Field { + name: "my-fielD2".into(), + field_conversion: Some(FieldConversion::Enum { + name: "mY-enum".into(), + variants: vec![EnumVariant { + name: "eNum-Variant".into(), + ..Default::default() + }], + }), + ..Default::default() + }, + ], + ..Default::default() + }), + Object::Buffer(Buffer { + name: "my-buffer".into(), + ..Default::default() + }), + ], + }; + + let end_mir = Device { + global_config, + objects: vec![ + Object::Register(Register { + name: "MyRegister".into(), + fields: vec![ + Field { + name: "my_field".into(), + ..Default::default() + }, + Field { + name: "my_field2".into(), + field_conversion: Some(FieldConversion::Enum { + name: "MyEnum".into(), + variants: vec![EnumVariant { + name: "EnumVariant".into(), + ..Default::default() + }], + }), + ..Default::default() + }, + ], + ..Default::default() + }), + Object::Buffer(Buffer { + name: "MyBuffer".into(), + ..Default::default() + }), + ], + }; + + run_pass(&mut start_mir).unwrap(); + + assert_eq!(start_mir, end_mir); + } +} diff --git a/generation/src/mir/passes/names_unique.rs b/generation/src/mir/passes/names_unique.rs new file mode 100644 index 00000000..9090146a --- /dev/null +++ b/generation/src/mir/passes/names_unique.rs @@ -0,0 +1,225 @@ +use std::collections::HashSet; + +use crate::mir::{Device, FieldConversion}; + +use super::recurse_objects; + +pub fn run_pass(device: &mut Device) -> anyhow::Result<()> { + let mut seen_object_names = HashSet::new(); + let mut generated_type_names = HashSet::new(); + + recurse_objects(&mut device.objects, &mut |object| { + anyhow::ensure!( + seen_object_names.insert(object.name().clone()), + "Duplicate object name found: \"{}\"", + object.name() + ); + + let mut seen_field_names = HashSet::new(); + for field in object + .fields() + .unwrap_or_else(|| Box::new(std::iter::empty())) + { + anyhow::ensure!( + seen_field_names.insert(field.name.clone()), + "Duplicate field name found in object \"{}\": \"{}\"", + object.name(), + field.name + ); + + if let Some(FieldConversion::Enum { name, variants }) = field.field_conversion.as_ref() + { + let mut seen_variant_names = HashSet::new(); + + anyhow::ensure!( + generated_type_names.insert(name.clone()), + "Duplicate generated enum name \"{}\" found in object \"{}\" on field \"{}\"", + name, + object.name(), + field.name, + ); + + for v in variants.iter() { + anyhow::ensure!( + seen_variant_names.insert(v.name.clone()), + "Duplicate field \"{}\" found in generated enum \"{}\" in object \"{}\" on field \"{}\"", + v.name, + name, + object.name(), + field.name, + ); + } + } + } + + Ok(()) + }) +} + +#[cfg(test)] +mod tests { + use convert_case::Boundary; + + use crate::mir::{Buffer, EnumVariant, Field, GlobalConfig, Object, Register}; + + use super::*; + + #[test] + #[should_panic(expected = "Duplicate object name found: \"MyBuffer\"")] + fn object_names_not_unique() { + let global_config = GlobalConfig { + default_register_access: Default::default(), + default_field_access: Default::default(), + default_buffer_access: Default::default(), + default_byte_order: Default::default(), + default_bit_order: Default::default(), + register_address_type: Default::default(), + command_address_type: Default::default(), + buffer_address_type: Default::default(), + name_word_boundaries: Boundary::list_from("-"), + }; + + let mut start_mir = Device { + global_config, + objects: vec![ + Object::Buffer(Buffer { + name: "MyBuffer".into(), + ..Default::default() + }), + Object::Buffer(Buffer { + name: "MyBuffer".into(), + ..Default::default() + }), + ], + }; + + run_pass(&mut start_mir).unwrap(); + } + + #[test] + #[should_panic(expected = "Duplicate field name found in object \"Reg\": \"field\"")] + fn field_names_not_unique() { + let global_config = GlobalConfig { + default_register_access: Default::default(), + default_field_access: Default::default(), + default_buffer_access: Default::default(), + default_byte_order: Default::default(), + default_bit_order: Default::default(), + register_address_type: Default::default(), + command_address_type: Default::default(), + buffer_address_type: Default::default(), + name_word_boundaries: Boundary::list_from("-"), + }; + + let mut start_mir = Device { + global_config, + objects: vec![Object::Register(Register { + name: "Reg".into(), + fields: vec![ + Field { + name: "field".into(), + ..Default::default() + }, + Field { + name: "field".into(), + ..Default::default() + }, + ], + ..Default::default() + })], + }; + + run_pass(&mut start_mir).unwrap(); + } + + #[test] + #[should_panic( + expected = "Duplicate generated enum name \"Enum\" found in object \"Reg\" on field \"field2\"" + )] + fn duplicate_generated_enums() { + let global_config = GlobalConfig { + default_register_access: Default::default(), + default_field_access: Default::default(), + default_buffer_access: Default::default(), + default_byte_order: Default::default(), + default_bit_order: Default::default(), + register_address_type: Default::default(), + command_address_type: Default::default(), + buffer_address_type: Default::default(), + name_word_boundaries: Boundary::list_from("-"), + }; + + let mut start_mir = Device { + global_config, + objects: vec![Object::Register(Register { + name: "Reg".into(), + fields: vec![ + Field { + name: "field".into(), + field_conversion: Some(FieldConversion::Enum { + name: "Enum".into(), + variants: Default::default(), + }), + ..Default::default() + }, + Field { + name: "field2".into(), + field_conversion: Some(FieldConversion::Enum { + name: "Enum".into(), + variants: Default::default(), + }), + ..Default::default() + }, + ], + ..Default::default() + })], + }; + + run_pass(&mut start_mir).unwrap(); + } + + #[test] + #[should_panic( + expected = "Duplicate field \"Variant\" found in generated enum \"Enum\" in object \"Reg\" on field \"field\"" + )] + fn duplicate_generated_enum_variants() { + let global_config = GlobalConfig { + default_register_access: Default::default(), + default_field_access: Default::default(), + default_buffer_access: Default::default(), + default_byte_order: Default::default(), + default_bit_order: Default::default(), + register_address_type: Default::default(), + command_address_type: Default::default(), + buffer_address_type: Default::default(), + name_word_boundaries: Boundary::list_from("-"), + }; + + let mut start_mir = Device { + global_config, + objects: vec![Object::Register(Register { + name: "Reg".into(), + fields: vec![Field { + name: "field".into(), + field_conversion: Some(FieldConversion::Enum { + name: "Enum".into(), + variants: vec![ + EnumVariant { + name: "Variant".into(), + ..Default::default() + }, + EnumVariant { + name: "Variant".into(), + ..Default::default() + }, + ], + }), + ..Default::default() + }], + ..Default::default() + })], + }; + + run_pass(&mut start_mir).unwrap(); + } +}