@@ -6,15 +6,66 @@ extern crate syn;
6
6
use proc_macro:: TokenStream ;
7
7
use proc_macro2:: TokenStream as TokenStream2 ;
8
8
use 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 } ;
11
11
12
12
struct PacNumberEnum {
13
13
name : Ident ,
14
14
valid_ranges : Vec < Range < usize > > ,
15
15
}
16
16
17
17
impl 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
+
18
69
fn valid_condition ( & self ) -> TokenStream2 {
19
70
let mut arms = Vec :: new ( ) ;
20
71
for range in & self . valid_ranges {
@@ -45,7 +96,7 @@ impl PacNumberEnum {
45
96
let const_name = TokenStream2 :: from_str ( const_name) . unwrap ( ) ;
46
97
47
98
quote ! {
48
- unsafe impl #trait_name for #name {
99
+ unsafe impl riscv_pac :: #trait_name for #name {
49
100
const #const_name: #num_type = #max_discriminant;
50
101
51
102
#[ inline]
@@ -67,93 +118,88 @@ impl PacNumberEnum {
67
118
}
68
119
}
69
120
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>)'" ) ;
122
191
}
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
- }
151
192
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 ( )
159
205
}
0 commit comments