@@ -6,15 +6,66 @@ extern crate syn;
66use proc_macro:: TokenStream ;
77use proc_macro2:: TokenStream as TokenStream2 ;
88use quote:: quote;
9- use std:: { collections:: HashMap , convert :: TryFrom , ops:: Range , str:: FromStr } ;
10- use syn:: { parse_macro_input, Data , DeriveInput , Error , Ident } ;
9+ use std:: { collections:: HashMap , ops:: Range , str:: FromStr } ;
10+ use syn:: { parse_macro_input, Data , DeriveInput , Ident } ;
1111
1212struct PacNumberEnum {
1313 name : Ident ,
1414 valid_ranges : Vec < Range < usize > > ,
1515}
1616
1717impl PacNumberEnum {
18+ fn new ( input : & DeriveInput ) -> Self {
19+ let variants = match & input. data {
20+ Data :: Enum ( data) => & data. variants ,
21+ _ => panic ! ( "Input is not an enum" ) ,
22+ } ;
23+
24+ // Collect the variants and their associated number discriminants
25+ let mut var_map = HashMap :: new ( ) ;
26+ let mut numbers = Vec :: new ( ) ;
27+ for variant in variants {
28+ let ident = & variant. ident ;
29+ let value = match & variant. discriminant {
30+ Some ( d) => match & d. 1 {
31+ syn:: Expr :: Lit ( expr_lit) => match & expr_lit. lit {
32+ syn:: Lit :: Int ( lit_int) => match lit_int. base10_parse :: < usize > ( ) {
33+ Ok ( num) => num,
34+ Err ( _) => panic ! ( "All variant discriminants must be unsigned integers" ) ,
35+ } ,
36+ _ => panic ! ( "All variant discriminants must be unsigned integers" ) ,
37+ } ,
38+ _ => panic ! ( "All variant discriminants must be unsigned integers" ) ,
39+ } ,
40+ _ => panic ! ( "Variant must have a discriminant" ) ,
41+ } ;
42+ // check for duplicate discriminant values
43+ var_map. insert ( value, ident) ;
44+ numbers. push ( value) ;
45+ }
46+
47+ // sort the number discriminants and generate a list of valid ranges
48+ numbers. sort_unstable ( ) ;
49+ let mut valid_ranges = Vec :: new ( ) ;
50+ let mut start = numbers[ 0 ] ;
51+ let mut end = start;
52+ for & number in & numbers[ 1 ..] {
53+ if number == end + 1 {
54+ end = number;
55+ } else {
56+ valid_ranges. push ( start..end + 1 ) ;
57+ start = number;
58+ end = start;
59+ }
60+ }
61+ valid_ranges. push ( start..end + 1 ) ;
62+
63+ Self {
64+ name : input. ident . clone ( ) ,
65+ valid_ranges,
66+ }
67+ }
68+
1869 fn valid_condition ( & self ) -> TokenStream2 {
1970 let mut arms = Vec :: new ( ) ;
2071 for range in & self . valid_ranges {
@@ -45,7 +96,7 @@ impl PacNumberEnum {
4596 let const_name = TokenStream2 :: from_str ( const_name) . unwrap ( ) ;
4697
4798 quote ! {
48- unsafe impl #trait_name for #name {
99+ unsafe impl riscv_pac :: #trait_name for #name {
49100 const #const_name: #num_type = #max_discriminant;
50101
51102 #[ inline]
@@ -67,93 +118,88 @@ impl PacNumberEnum {
67118 }
68119}
69120
70- impl TryFrom < DeriveInput > for PacNumberEnum {
71- type Error = Error ;
72-
73- fn try_from ( input : DeriveInput ) -> Result < Self , Self :: Error > {
74- let variants = match & input. data {
75- Data :: Enum ( data) => & data. variants ,
76- _ => panic ! ( "Input is not an enum" ) ,
77- } ;
78-
79- // Collect the variants and their associated number discriminants
80- let mut var_map = HashMap :: new ( ) ;
81- let mut numbers = Vec :: new ( ) ;
82- for variant in variants {
83- let ident = & variant. ident ;
84- let value = match & variant. discriminant {
85- Some ( d) => match & d. 1 {
86- syn:: Expr :: Lit ( expr_lit) => match & expr_lit. lit {
87- syn:: Lit :: Int ( lit_int) => match lit_int. base10_parse :: < usize > ( ) {
88- Ok ( num) => num,
89- Err ( _) => panic ! ( "All variant discriminants must be unsigned integers" ) ,
90- } ,
91- _ => panic ! ( "All variant discriminants must be unsigned integers" ) ,
92- } ,
93- _ => panic ! ( "All variant discriminants must be unsigned integers" ) ,
94- } ,
95- _ => panic ! ( "Variant must have a discriminant" ) ,
96- } ;
97- // check for duplicate discriminant values
98- var_map. insert ( value, ident) ;
99- numbers. push ( value) ;
100- }
101-
102- // sort the number discriminants and generate a list of valid ranges
103- numbers. sort_unstable ( ) ;
104- let mut valid_ranges = Vec :: new ( ) ;
105- let mut start = numbers[ 0 ] ;
106- let mut end = start;
107- for & number in & numbers[ 1 ..] {
108- if number == end + 1 {
109- end = number;
110- } else {
111- valid_ranges. push ( start..end + 1 ) ;
112- start = number;
113- end = start;
114- }
115- }
116- valid_ranges. push ( start..end + 1 ) ;
117-
118- Ok ( PacNumberEnum {
119- name : input. ident . clone ( ) ,
120- valid_ranges,
121- } )
121+ /// Attribute-like macro that implements the traits of the `riscv-pac` crate for a given enum.
122+ ///
123+ /// As these traits are unsafe, the macro must be called with the `unsafe` keyword followed by the trait name.
124+ /// In this way, we warn callers that they must comply with the requirements of the trait.
125+ ///
126+ /// The trait name must be one of `ExceptionNumber`, `InterruptNumber`, `PriorityNumber`, or `HartIdNumber`.
127+ /// Marker traits `CoreInterruptNumber` and `ExternalInterruptNumber` cannot be implemented using this macro.
128+ ///
129+ /// # Note
130+ ///
131+ /// To implement number-to-enum operation, the macro works with ranges of valid discriminant numbers.
132+ /// If the number is within any of the valid ranges, the number is transmuted to the enum variant.
133+ /// In this way, the macro achieves better performance for enums with a large number of consecutive variants.
134+ /// Thus, the enum must comply with the following requirements:
135+ ///
136+ /// - All the enum variants must have a valid discriminant number (i.e., a number that is within the valid range of the enum).
137+ /// - For the `ExceptionNumber`, `InterruptNumber`, and `HartIdNumber` traits, the enum must be annotated as `#[repr(u16)]`
138+ /// - For the `PriorityNumber` trait, the enum must be annotated as `#[repr(u8)]`
139+ ///
140+ /// If the enum does not meet these requirements, you will have to implement the traits manually (e.g., `riscv::mcause::Interrupt`).
141+ /// For enums with a small number of consecutive variants, it might be better to implement the traits manually.
142+ ///
143+ /// # Safety
144+ ///
145+ /// The struct to be implemented must comply with the requirements of the specified trait.
146+ ///
147+ /// # Example
148+ ///
149+ /// ```rust
150+ /// use riscv_pac::*;
151+ ///
152+ /// #[repr(u16)]
153+ /// #[pac_enum(unsafe ExceptionNumber)]
154+ /// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
155+ /// enum Exception {
156+ /// E1 = 1,
157+ /// E3 = 3,
158+ /// }
159+ ///
160+ /// fn main() {
161+ /// assert_eq!(Exception::E1.number(), 1);
162+ /// assert_eq!(Exception::E3.number(), 3);
163+ ///
164+ /// assert_eq!(Exception::from_number(1), Ok(Exception::E1));
165+ /// assert_eq!(Exception::from_number(2), Err(2));
166+ /// assert_eq!(Exception::from_number(3), Ok(Exception::E3));
167+ ///
168+ /// assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3);
169+ /// }
170+ ///```
171+ #[ proc_macro_attribute]
172+ pub fn pac_enum ( attr : TokenStream , item : TokenStream ) -> TokenStream {
173+ let input = parse_macro_input ! ( item as DeriveInput ) ;
174+ let pac_enum = PacNumberEnum :: new ( & input) ;
175+
176+ // attr should be unsafe ExceptionNumber, unsafe InterruptNumber, unsafe PriorityNumber, or unsafe HartIdNumber
177+ // assert that attribute starts with the unsafe token. If not, raise a panic error
178+ let attr = attr. to_string ( ) ;
179+ // split string into words and check if the first word is "unsafe"
180+ let attrs = attr. split_whitespace ( ) . collect :: < Vec < & str > > ( ) ;
181+ if attrs. is_empty ( ) {
182+ panic ! ( "Attribute is empty. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'" ) ;
183+ }
184+ if attrs. len ( ) > 2 {
185+ panic ! (
186+ "Wrong attribute format. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'"
187+ ) ;
188+ }
189+ if attrs[ 0 ] != "unsafe" {
190+ panic ! ( "Attribute does not start with 'unsafe'. Expected: 'riscv_pac::pac_enum(unsafe <PacTraitToImplement>)'" ) ;
122191 }
123- }
124-
125- #[ proc_macro_derive( ExceptionNumber ) ]
126- pub fn exception_number_derive ( input : TokenStream ) -> TokenStream {
127- let input = parse_macro_input ! ( input as DeriveInput ) ;
128- let pac_enum = PacNumberEnum :: try_from ( input) . unwrap ( ) ;
129- pac_enum
130- . quote ( "ExceptionNumber" , "u16" , "MAX_EXCEPTION_NUMBER" )
131- . into ( )
132- }
133-
134- #[ proc_macro_derive( InterruptNumber ) ]
135- pub fn interrupt_number_derive ( input : TokenStream ) -> TokenStream {
136- let input = parse_macro_input ! ( input as DeriveInput ) ;
137- let pac_enum = PacNumberEnum :: try_from ( input) . unwrap ( ) ;
138- pac_enum
139- . quote ( "InterruptNumber" , "u16" , "MAX_INTERRUPT_NUMBER" )
140- . into ( )
141- }
142-
143- #[ proc_macro_derive( PriorityNumber ) ]
144- pub fn priority_number_derive ( input : TokenStream ) -> TokenStream {
145- let input = parse_macro_input ! ( input as DeriveInput ) ;
146- let pac_enum = PacNumberEnum :: try_from ( input) . unwrap ( ) ;
147- pac_enum
148- . quote ( "PriorityNumber" , "u8" , "MAX_PRIORITY_NUMBER" )
149- . into ( )
150- }
151192
152- #[ proc_macro_derive( HartIdNumber ) ]
153- pub fn hart_id_number_derive ( input : TokenStream ) -> TokenStream {
154- let input = parse_macro_input ! ( input as DeriveInput ) ;
155- let pac_enum = PacNumberEnum :: try_from ( input) . unwrap ( ) ;
156- pac_enum
157- . quote ( "HartIdNumber" , "u16" , "MAX_HART_ID_NUMBER" )
158- . into ( )
193+ let trait_impl = match attrs[ 1 ] {
194+ "ExceptionNumber" => pac_enum. quote ( "ExceptionNumber" , "u16" , "MAX_EXCEPTION_NUMBER" ) ,
195+ "InterruptNumber" => pac_enum. quote ( "InterruptNumber" , "u16" , "MAX_INTERRUPT_NUMBER" ) ,
196+ "PriorityNumber" => pac_enum. quote ( "PriorityNumber" , "u8" , "MAX_PRIORITY_NUMBER" ) ,
197+ "HartIdNumber" => pac_enum. quote ( "HartIdNumber" , "u16" , "MAX_HART_ID_NUMBER" ) ,
198+ _ => panic ! ( "Unknown trait '{}'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber'" , attrs[ 1 ] ) ,
199+ } ;
200+ quote ! {
201+ #input
202+ #trait_impl
203+ }
204+ . into ( )
159205}
0 commit comments