diff --git a/bitbybit/Cargo.toml b/bitbybit/Cargo.toml index fb9e8fd..73a50a5 100644 --- a/bitbybit/Cargo.toml +++ b/bitbybit/Cargo.toml @@ -18,6 +18,7 @@ introspect = [] extra-traits = ["syn/extra-traits"] # Required for the field example to work. examples = ["arbitrary-int/defmt"] +default = ["extra-traits"] [dependencies] syn = { version = "2.0", features = ["full"] } diff --git a/bitbybit/src/bitfield/codegen.rs b/bitbybit/src/bitfield/codegen.rs index 420d071..3541e31 100644 --- a/bitbybit/src/bitfield/codegen.rs +++ b/bitbybit/src/bitfield/codegen.rs @@ -2,7 +2,7 @@ use crate::bitfield::{ const_name, mask_name, setter_name, with_name, ArrayInfo, BaseDataSize, BitfieldAttributes, CustomType, DefmtVariant, FieldDefinition, BITCOUNT_BOOL, }; -use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream, TokenTree}; +use proc_macro2::{Ident, Literal, TokenStream as TokenStream2, TokenStream, TokenTree}; use quote::{quote, TokenStreamExt as _}; use std::ops::Range; use std::str::FromStr; @@ -121,7 +121,8 @@ pub fn generate( quote! {} }; - let setter = if let Some(setter_type) = field_definition.setter_type.as_ref() { + let setter = { + let setter_type = field_definition.setter_type.clone() ; let argument_converted = match field_definition.custom_type { CustomType::No => { @@ -159,12 +160,22 @@ pub fn generate( let setter_name = setter_name(field_name); let with_name = with_name(field_name); + let public_status = if field_definition.setter_is_public { + quote! { + pub + } + } else { + quote!{ + + } + } ; + if let Some(array) = field_definition.array { let indexed_count = array.count; quote! { #(#doc_comment)* #[inline] - pub const fn #with_name(&self, index: usize, field_value: #setter_type) -> Self { + #public_status const fn #with_name(&self, index: usize, field_value: #setter_type) -> Self { assert!(index < #indexed_count); Self { raw_value: #new_raw_value @@ -172,7 +183,7 @@ pub fn generate( } #(#doc_comment)* #[inline] - pub fn #setter_name(&mut self, index: usize, field_value: #setter_type) { + #public_status fn #setter_name(&mut self, index: usize, field_value: #setter_type) { assert!(index < #indexed_count); self.raw_value = #new_raw_value; } @@ -181,21 +192,22 @@ pub fn generate( quote! { #(#doc_comment)* #[inline] - pub const fn #with_name(&self, field_value: #setter_type) -> Self { + #public_status const fn #with_name(&self, field_value: #setter_type) -> Self { Self { raw_value: #new_raw_value } } #(#doc_comment)* #[inline] - pub fn #setter_name(&mut self, field_value: #setter_type) { + #public_status fn #setter_name(&mut self, field_value: #setter_type) { self.raw_value = #new_raw_value; } } } - } else { - quote! {} }; + + + quote! { #introspect @@ -207,6 +219,58 @@ pub fn generate( accessors } +pub fn generate_field_defaults(field_definitions: &[FieldDefinition]) -> TokenStream { + let mut default_constructor = Vec::new(); + let mut default_consts = Vec::new(); + + let default_from_fields = super::DefaultVal::UseFieldDefault ; + default_constructor.push(quote! { + pub const #default_from_fields: Self = Self::ZERO + }); + for field_definition in field_definitions { + if let Some(default_value) = &field_definition.default_value { + let field_name = &field_definition.field_name; + let func_name = with_name(field_name); + let default_const = const_name(field_name, "DEFAULT"); + + let field_type = field_definition.setter_type.clone(); + let converted_field_value = default_value; + + // 3 cases: either a custom-type, or a basic Rust number type, or an arbitrary-int type + let const_def = if let CustomType::Yes(_) = field_definition.custom_type { + quote! { + // Custom type + pub const #default_const: #field_type = #converted_field_value; + } + } + else if field_definition.use_regular_int { + // Basic Rust number type + quote! { + pub const #default_const: #field_type = #converted_field_value; + } + } else { + // Arbitrary-int are created with ::new() + quote! { + pub const #default_const: #field_type = #field_type::new(#converted_field_value); + } + }; + + default_constructor.push(quote! { + .#func_name(Self::#default_const) + }); + default_consts.push(const_def); + } + } + default_constructor.push(quote! { + ; + }); + + quote! { + #(#default_consts)* + #(#default_constructor)* + } +} + /// If there are multiple ranges, this packs them together fn getter_packed( field_definition: &FieldDefinition, @@ -436,7 +500,8 @@ pub fn make_builder( }); for field_definition in field_definitions { - if let Some(setter_type) = field_definition.setter_type.as_ref() { + if field_definition.setter_is_public { + let setter_type = field_definition.setter_type.clone() ; let field_name = &field_definition.field_name; let with_name = with_name(field_name); diff --git a/bitbybit/src/bitfield/mod.rs b/bitbybit/src/bitfield/mod.rs index 6cc4b95..e384a3b 100644 --- a/bitbybit/src/bitfield/mod.rs +++ b/bitbybit/src/bitfield/mod.rs @@ -7,6 +7,7 @@ use proc_macro2::Ident; use proc_macro2::TokenStream as TokenStream2; use quote::TokenStreamExt; use quote::{quote, ToTokens}; +use syn::Expr; use std::ops::Range; use std::str::FromStr; use syn::meta::ParseNestedMeta; @@ -66,7 +67,8 @@ struct FieldDefinition { array: Option, field_type_size: usize, getter_type: Option, - setter_type: Option, + setter_type: Type, + setter_is_public: bool, field_type_size_from_data_type: Option, is_signed: bool, /// If non-null: (count, stride) @@ -74,6 +76,7 @@ struct FieldDefinition { primitive_type: TokenStream2, custom_type: CustomType, doc_comment: Vec, + default_value: Option, //TODO - check if it's the correct type } // If a convert_type is given, that will be the final getter/setter type. If not, it is the base type @@ -115,10 +118,12 @@ impl BaseDataSize { } } -#[cfg_attr(feature = "extra-traits", derive(Debug))] +#[cfg_attr(feature = "extra-traits", derive(Debug, Clone, PartialEq, Eq))] pub enum DefaultVal { Lit(LitInt), Constant(Ident), + Expr(Expr), + UseFieldDefault, } impl ToTokens for DefaultVal { @@ -126,6 +131,11 @@ impl ToTokens for DefaultVal { match self { DefaultVal::Lit(lit) => lit.to_tokens(tokens), DefaultVal::Constant(ident) => ident.to_tokens(tokens), + DefaultVal::Expr(expr) => expr.to_tokens(tokens), + DefaultVal::UseFieldDefault => { + syn::parse_str::("DEFAULTS_FROM_FIELDS") + .unwrap_or_else(|_| panic!("bitfield!: Error creating setter name")) + }.to_tokens(tokens), } } } @@ -176,7 +186,12 @@ impl BitfieldAttributes { } let path: Result = stream.parse(); if let Ok(path) = path { - self.default_val = Some(DefaultVal::Constant(path)); + if path == "use_field_defaults" { + self.default_val = Some(DefaultVal::UseFieldDefault); + } else { + self.default_val = Some(DefaultVal::Constant(path)); + } + return Ok(()); } return Ok(()); @@ -305,29 +320,54 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream { let (default_constructor, default_trait) = if let Some(default_value) = &bitfield_attrs.default_val { - let default_value = default_value.to_token_stream(); - let constructor = { - let comment = format!("An instance that uses the default value {}", default_value); - let deprecated_warning = format!( - "Use {}::Default (or {}::DEFAULT in const context) instead", - struct_name, struct_name - ); - - let default_raw_value = if base_data_size.exposed == base_data_size.internal { - quote! { const DEFAULT_RAW_VALUE: #base_data_type = #default_value; } - } else { - quote! { const DEFAULT_RAW_VALUE: #base_data_type = #base_data_type::new(#default_value); } - }; - quote! { - #default_raw_value - - #[doc = #comment] - pub const DEFAULT: Self = Self::new_with_raw_value(Self::DEFAULT_RAW_VALUE); - - /// Creates a new instance of this struct using the default value - #[deprecated(note = #deprecated_warning)] - pub const fn new() -> Self { - Self::DEFAULT + let constructor = match default_value { + DefaultVal::UseFieldDefault => { + let default_value = default_value.to_token_stream(); + let comment = format!("An instance that uses the default value {}", default_value); + let deprecated_warning = format!( + "Use {}::Default (or {}::DEFAULT in const context) instead", + struct_name, struct_name + ); + + quote! { + #[doc = #comment] + pub const DEFAULT: Self = Self::#default_value; + + /// Creates a new instance of this struct using the default value + #[deprecated(note = #deprecated_warning)] + pub const fn new() -> Self { + Self::DEFAULT + } + + #[doc = #comment] + const DEFAULT_RAW_VALUE: #base_data_type = Self::#default_value.raw_value(); + } + } + _ => { + let default_value = default_value.to_token_stream(); + + let comment = format!("An instance that uses the default value {}", default_value); + let deprecated_warning = format!( + "Use {}::Default (or {}::DEFAULT in const context) instead", + struct_name, struct_name + ); + + let default_raw_value = if base_data_size.exposed == base_data_size.internal { + quote! { const DEFAULT_RAW_VALUE: #base_data_type = #default_value; } + } else { + quote! { const DEFAULT_RAW_VALUE: #base_data_type = #base_data_type::new(#default_value); } + }; + quote! { + #default_raw_value + + #[doc = #comment] + pub const DEFAULT: Self = Self::new_with_raw_value(Self::DEFAULT_RAW_VALUE); + + /// Creates a new instance of this struct using the default value + #[deprecated(note = #deprecated_warning)] + pub const fn new() -> Self { + Self::DEFAULT + } } } }; @@ -412,6 +452,11 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream { zero ); + //TODO - put this behing a feature gate + let field_defaults = codegen::generate_field_defaults(&field_definitions); + let field_defaults_module = syn::parse_str::(format!("{}Implementation", struct_name).as_str()) + .unwrap_or_else(|_| panic!("bitfield!: Error parsing internal base data type")); + let expanded = quote! { #[derive(Copy, Clone)] #[repr(C)] @@ -438,8 +483,18 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream { #new_with_constructor - #( #accessors )* } + + + mod #field_defaults_module { + use super::* ; + impl super::#struct_name { + #( #accessors )* + + #field_defaults + } + } + #default_trait #debug_trait diff --git a/bitbybit/src/bitfield/parsing.rs b/bitbybit/src/bitfield/parsing.rs index 1ad55ed..ff1335a 100644 --- a/bitbybit/src/bitfield/parsing.rs +++ b/bitbybit/src/bitfield/parsing.rs @@ -6,7 +6,7 @@ use proc_macro2::{Ident, Literal, Punct, TokenStream as TokenStream2, TokenTree} use quote::{quote, ToTokens}; use std::ops::{Deref, Range}; use syn::{ - parse2, spanned::Spanned, Attribute, Error, ExprArray, Field, Fields, GenericArgument, + parse2, spanned::Spanned, Attribute, Error, ExprArray, Field, Fields, GenericArgument, Lit, MetaList, PathArguments, Result, Type, }; @@ -167,6 +167,7 @@ fn parse_field(base_data_size: usize, field: &Field) -> Result let mut provide_getter = false; let mut provide_setter = false; let mut indexed_stride: Option = None; + let mut default_value = None; let mut doc_comment: Vec = Vec::new(); @@ -262,13 +263,29 @@ fn parse_field(base_data_size: usize, field: &Field) -> Result } indexed_stride = Some(stride); } + ArgumentParser::DefaultParsing(value) => { + let expression: Result = syn::parse_str(&value); + if let Ok(expression) = expression { + default_value = Some(super::DefaultVal::Expr(expression)); + } else { + Err(Error::new_spanned( + &attr.meta, + format!( + "bitfield!: Invalid syntax. This can't be parsed into a valid expression: {}", value + )))? ; + } + + } ArgumentParser::Reset => { // An empty argument (happens after arrays as that's a separate ArgumentParser) } _ => Err(Error::new_spanned( &range_span, - "bitfield!: Invalid syntax. Supported: bits(5..=6, access, stride = x), where x is an integer and access can be r, w or rw", + format!( +"bitfield!: Invalid syntax. Supported: bits(5..=6, access, stride = x), where x is an integer and access can be r, w or rw (debug: {:?})", range_parser + ) + , ))?, }; @@ -386,6 +403,11 @@ fn parse_field(base_data_size: usize, field: &Field) -> Result number_of_bits != 1 && is_int_size_regular_type(number_of_bits) } }; + let is_signed = field_type_size_from_data_type.is_some_and(|v| v.1); + + //if let Some(default_value) = default_value { + // //verify_value_fits_in_type(field, default_value, field_type_size, is_signed)?; + //} Ok(FieldDefinition { field_name: field_name.clone(), @@ -396,11 +418,8 @@ fn parse_field(base_data_size: usize, field: &Field) -> Result } else { None }, - setter_type: if provide_setter { - Some(setter_type) - } else { - None - }, + setter_type, + setter_is_public: provide_setter, use_regular_int, primitive_type, custom_type, @@ -410,8 +429,9 @@ fn parse_field(base_data_size: usize, field: &Field) -> Result indexed_stride: indexed_stride.unwrap(), }), field_type_size_from_data_type: field_type_size_from_data_type.map(|v| v.0), - is_signed: field_type_size_from_data_type.is_some_and(|v| v.1), + is_signed, unsigned_field_type, + default_value, }) } @@ -496,9 +516,41 @@ fn verify_bounds_for_array( Ok(()) } +fn verify_value_fits_in_type( + field: &Field, + value: isize, + field_type_size: usize, + is_signed: bool, +) -> syn::Result<()> { + let (upper_bound, lower_bound) = if is_signed { + ( + 2isize.pow(field_type_size as u32 - 1) - 1, + -2isize.pow(field_type_size as u32 - 1) + 1, + ) + } else { + (2isize.pow(field_type_size as u32) - 1, 0) + }; + + if value > upper_bound || value < lower_bound { + Err(Error::new_spanned( + &field, + format!( + "bitfield!: value {} does not fit in {} {} bits ( [{}:{}] )", + value, + field_type_size, + if is_signed { "signed" } else { "unsigned" }, + lower_bound, + upper_bound, + ), + )) + } else { + Ok(()) + } +} + /// Parses the arguments of a field. At the beginning and after each comma, Reset is used. After /// that, the various take_xxx functions are used to switch states. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] enum ArgumentParser { // An unknown argument - could go into any of the categories below Reset, @@ -522,6 +574,10 @@ enum ArgumentParser { Read, Write, ReadWrite, + + // Default value for a field + DefaultStarted, + DefaultParsing(String), } impl ArgumentParser { @@ -544,6 +600,12 @@ impl ArgumentParser { ArgumentParser::HasStrideEquals => Ok(ArgumentParser::StrideComplete( Self::parse_literal_number(lit)?, )), + ArgumentParser::DefaultStarted => Ok(ArgumentParser::DefaultParsing(lit.to_string())), + ArgumentParser::DefaultParsing(str) => { + let mut str = str.to_string(); + str.push_str(&lit.to_string()); + Ok(ArgumentParser::DefaultParsing(str)) + } _ => Err(Error::new_spanned( &lit, "bitfield!: Invalid bit-range. Expected x..=y, for example 6..=10.", @@ -565,6 +627,14 @@ impl ArgumentParser { ArgumentParser::StrideStarted if punct.as_char() == '=' || punct.as_char() == ':' => { Ok(ArgumentParser::HasStrideEquals) } + ArgumentParser::DefaultStarted if punct.as_char() == '=' || punct.as_char() == ':' => { + Ok(ArgumentParser::DefaultParsing("".to_string())) + } + ArgumentParser::DefaultParsing(str) => { + let mut str = str.to_string(); + str.push(punct.as_char()); + Ok(ArgumentParser::DefaultParsing(str)) + } _ => Err(Error::new_spanned( &punct, "bitfield!: Invalid bit-range. Expected x..=y, for example 6..=10.", @@ -579,10 +649,16 @@ impl ArgumentParser { ArgumentParser::Reset if s == "r" => Ok(ArgumentParser::Read), ArgumentParser::Reset if s == "w" => Ok(ArgumentParser::Write), ArgumentParser::Reset if s == "stride" => Ok(ArgumentParser::StrideStarted), + ArgumentParser::Reset if s == "default" => Ok(ArgumentParser::DefaultStarted), + ArgumentParser::DefaultParsing(str) => { + let mut str = str.to_string(); + str.push_str(&s); + Ok(ArgumentParser::DefaultParsing(str)) + } _ => Err(Error::new_spanned( &id, format!( - "bitfield!: Invalid ident '{}'. Expected r, rw, w or stride", + "bitfield!: Invalid ident '{}'. Expected r, rw, w, stride or default", s ), )), @@ -605,7 +681,7 @@ impl ArgumentParser { Self::Reset }; let mut token_id = 0; - let mut argument_parser = reset; + let mut argument_parser = reset.clone(); for meta in token_stream { match meta { TokenTree::Group(group) => { @@ -629,7 +705,7 @@ impl ArgumentParser { is_in_array, outer_token_id.unwrap_or(token_id), )?; - argument_parser = reset; + argument_parser = reset.clone(); //because reset isn't copy anymore } _ => { argument_parser = argument_parser.take_punct(punct)?; diff --git a/bitbybit/tests/default_values.rs b/bitbybit/tests/default_values.rs new file mode 100644 index 0000000..b2aa0ca --- /dev/null +++ b/bitbybit/tests/default_values.rs @@ -0,0 +1,76 @@ +use arbitrary_int::*; +use bitbybit::{bitenum, bitfield}; + +#[bitenum(u2, exhaustive = true)] +#[derive(PartialEq, Debug)] +pub enum BitEnumTest { + A = 0, + B = 1, + C = 2, + D = 3 +} + + +#[bitfield(u32, introspect, forbid_overlaps, default = use_field_defaults)] +pub struct WithDefaultFieldValues { + #[bit(0, rw, default=true)] + test_bool_true: bool, + #[bit(1, rw, default=false)] + test_bool_false: bool, + #[bit(2, rw, default=1)] + test_u1: u1, + #[bits(3..=6, rw, default=10)] + test_arbitrary_int: u4, + #[bits(7..=8, rw, default= BitEnumTest::B)] + test_enum: BitEnumTest, + #[bits(9..=16, rw, default=0xFF)] + test_native_int: u8, + #[bits(17..=31, rw, default=-50)] + test_signed_int: i15, +} + +#[bitfield(u32, introspect, default = 0xFFFF_0000)] // the previous system takes over +pub struct LegacyDefault { + #[bits(0..=15, rw, default = 4)] + non_zero: u16, + #[bits(16..=31, rw)] + zero: u16, +} + + +pub fn main() { + // Testing the generated constant + assert_eq!(WithDefaultFieldValues::TEST_BOOL_TRUE_DEFAULT, true); + assert_eq!(WithDefaultFieldValues::TEST_BOOL_FALSE_DEFAULT, false); + assert_eq!(WithDefaultFieldValues::TEST_U1_DEFAULT, u1::new(1)); + assert_eq!(WithDefaultFieldValues::TEST_ARBITRARY_INT_DEFAULT, u4::new(10)); + assert_eq!(WithDefaultFieldValues::TEST_ENUM_DEFAULT, BitEnumTest::B); + assert_eq!(WithDefaultFieldValues::TEST_NATIVE_INT_DEFAULT, 0xFF); + assert_eq!(WithDefaultFieldValues::TEST_SIGNED_INT_DEFAULT, i15::new(-50)); + + // Test the generated default value + let reg = WithDefaultFieldValues::default(); + assert_eq!(reg.test_bool_true(), true); + assert_eq!(reg.test_bool_false(), false); + assert_eq!(reg.test_u1(), u1::new(1)); + assert_eq!(reg.test_arbitrary_int(), u4::new(10)); + assert_eq!(reg.test_enum(), BitEnumTest::B); + assert_eq!(reg.test_native_int(), 0xFF); + assert_eq!(reg.test_signed_int(), i15::new(-50)); + + // Still can access the previous ZERO const + let reg = WithDefaultFieldValues::ZERO; + assert_eq!(reg.test_bool_true(), false); + assert_eq!(reg.test_bool_false(), false); + assert_eq!(reg.test_u1(), u1::new(0)); + assert_eq!(reg.test_arbitrary_int(), u4::new(0)); + assert_eq!(reg.test_enum(), BitEnumTest::A); + assert_eq!(reg.test_native_int(), 00); + assert_eq!(reg.test_signed_int(), i15::new(0)); + + // Old system can still be used + let reg: LegacyDefault = LegacyDefault::default() ; + assert_eq!(reg.non_zero(), 0x0000); + assert_eq!(reg.zero(), 0xFFFF); + +} diff --git a/bitbybit/tests/tests.rs b/bitbybit/tests/tests.rs index 8702d91..a2f2758 100644 --- a/bitbybit/tests/tests.rs +++ b/bitbybit/tests/tests.rs @@ -5,6 +5,7 @@ fn all_tests() { // tests that pass t.pass("tests/basic.rs"); t.pass("tests/with_fields.rs"); + t.pass("tests/default_values.rs"); t.compile_fail("tests/no_compile/exhaustive_bitenum.rs"); t.compile_fail("tests/no_compile/non_exhaustive_bitenum.rs");