1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Utility functions for dealing with Rust integer types.
16 
17 use crate::backends::rust_legacy::ToIdent;
18 use crate::{analyzer, ast};
19 use quote::{format_ident, quote};
20 
21 /// A Rust integer type such as `u8`.
22 #[derive(Copy, Clone)]
23 pub struct Integer {
24     pub width: usize,
25 }
26 
27 impl Integer {
28     /// Get the Rust integer type for the given bit width.
29     ///
30     /// This will round up the size to the nearest Rust integer size.
31     /// PDL supports integers up to 64 bit, so it is an error to call
32     /// this with a width larger than 64.
new(width: usize) -> Integer33     pub fn new(width: usize) -> Integer {
34         for integer_width in [8, 16, 32, 64] {
35             if width <= integer_width {
36                 return Integer { width: integer_width };
37             }
38         }
39         panic!("Cannot construct Integer with width: {width}")
40     }
41 }
42 
43 impl quote::ToTokens for Integer {
to_tokens(&self, tokens: &mut proc_macro2::TokenStream)44     fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
45         let t: syn::Type = syn::parse_str(&format!("u{}", self.width))
46             .expect("Could not parse integer, unsupported width?");
47         t.to_tokens(tokens);
48     }
49 }
50 
rust_type(field: &ast::Field) -> proc_macro2::TokenStream51 pub fn rust_type(field: &ast::Field) -> proc_macro2::TokenStream {
52     match &field.desc {
53         ast::FieldDesc::Scalar { width, .. } if field.cond.is_some() => {
54             let field_type = Integer::new(*width);
55             quote!(Option<#field_type>)
56         }
57         ast::FieldDesc::Scalar { width, .. } => {
58             let field_type = Integer::new(*width);
59             quote!(#field_type)
60         }
61         ast::FieldDesc::Typedef { type_id, .. } if field.cond.is_some() => {
62             let field_type = type_id.to_ident();
63             quote!(Option<#field_type>)
64         }
65         ast::FieldDesc::Typedef { type_id, .. } => {
66             let field_type = type_id.to_ident();
67             quote!(#field_type)
68         }
69         ast::FieldDesc::Array { width: Some(width), size: Some(size), .. } => {
70             let field_type = Integer::new(*width);
71             let size = proc_macro2::Literal::usize_unsuffixed(*size);
72             quote!([#field_type; #size])
73         }
74         ast::FieldDesc::Array { width: Some(width), size: None, .. } => {
75             let field_type = Integer::new(*width);
76             quote!(Vec<#field_type>)
77         }
78         ast::FieldDesc::Array { type_id: Some(type_id), size: Some(size), .. } => {
79             let field_type = type_id.to_ident();
80             let size = proc_macro2::Literal::usize_unsuffixed(*size);
81             quote!([#field_type; #size])
82         }
83         ast::FieldDesc::Array { type_id: Some(type_id), size: None, .. } => {
84             let field_type = type_id.to_ident();
85             quote!(Vec<#field_type>)
86         }
87         //ast::Field::Size { .. } | ast::Field::Count { .. } => quote!(),
88         _ => todo!("{field:?}"),
89     }
90 }
91 
rust_borrow(field: &ast::Field, scope: &analyzer::Scope<'_>) -> proc_macro2::TokenStream92 pub fn rust_borrow(field: &ast::Field, scope: &analyzer::Scope<'_>) -> proc_macro2::TokenStream {
93     match &field.desc {
94         ast::FieldDesc::Scalar { .. } => quote!(),
95         ast::FieldDesc::Typedef { type_id, .. } => match &scope.typedef[type_id].desc {
96             ast::DeclDesc::Enum { .. } => quote!(),
97             ast::DeclDesc::Struct { .. } => quote!(&),
98             ast::DeclDesc::CustomField { .. } => quote!(),
99             desc => unreachable!("unexpected declaration: {desc:?}"),
100         },
101         ast::FieldDesc::Array { .. } => quote!(&),
102         _ => todo!(),
103     }
104 }
105 
106 /// Suffix for `Buf::get_*` and `BufMut::put_*` methods when reading a
107 /// value with the given `width`.
endianness_suffix(endianness: ast::EndiannessValue, width: usize) -> &'static str108 fn endianness_suffix(endianness: ast::EndiannessValue, width: usize) -> &'static str {
109     if width > 8 && endianness == ast::EndiannessValue::LittleEndian {
110         "_le"
111     } else {
112         ""
113     }
114 }
115 
116 /// Parse an unsigned integer with the given `width`.
117 ///
118 /// The generated code requires that `span` is a mutable `bytes::Buf`
119 /// value.
get_uint( endianness: ast::EndiannessValue, width: usize, span: &proc_macro2::Ident, ) -> proc_macro2::TokenStream120 pub fn get_uint(
121     endianness: ast::EndiannessValue,
122     width: usize,
123     span: &proc_macro2::Ident,
124 ) -> proc_macro2::TokenStream {
125     let suffix = endianness_suffix(endianness, width);
126     let value_type = Integer::new(width);
127     if value_type.width == width {
128         let get_u = format_ident!("get_u{}{}", value_type.width, suffix);
129         quote! {
130             #span.get_mut().#get_u()
131         }
132     } else {
133         let get_uint = format_ident!("get_uint{}", suffix);
134         let value_nbytes = proc_macro2::Literal::usize_unsuffixed(width / 8);
135         let cast = (value_type.width < 64).then(|| quote!(as #value_type));
136         quote! {
137             #span.get_mut().#get_uint(#value_nbytes) #cast
138         }
139     }
140 }
141 
142 /// Write an unsigned integer `value` to `span`.
143 ///
144 /// The generated code requires that `span` is a mutable
145 /// `bytes::BufMut` value.
put_uint( endianness: ast::EndiannessValue, value: &proc_macro2::TokenStream, width: usize, span: &proc_macro2::Ident, ) -> proc_macro2::TokenStream146 pub fn put_uint(
147     endianness: ast::EndiannessValue,
148     value: &proc_macro2::TokenStream,
149     width: usize,
150     span: &proc_macro2::Ident,
151 ) -> proc_macro2::TokenStream {
152     let suffix = endianness_suffix(endianness, width);
153     let value_type = Integer::new(width);
154     if value_type.width == width {
155         let put_u = format_ident!("put_u{}{}", width, suffix);
156         quote! {
157             #span.#put_u(#value)
158         }
159     } else {
160         let put_uint = format_ident!("put_uint{}", suffix);
161         let value_nbytes = proc_macro2::Literal::usize_unsuffixed(width / 8);
162         let cast = (value_type.width < 64).then(|| quote!(as u64));
163         quote! {
164             #span.#put_uint(#value #cast, #value_nbytes)
165         }
166     }
167 }
168 
169 #[cfg(test)]
170 mod tests {
171     use super::*;
172 
173     #[test]
test_integer_new()174     fn test_integer_new() {
175         assert_eq!(Integer::new(0).width, 8);
176         assert_eq!(Integer::new(8).width, 8);
177         assert_eq!(Integer::new(9).width, 16);
178         assert_eq!(Integer::new(64).width, 64);
179     }
180 
181     #[test]
182     #[should_panic]
test_integer_new_panics_on_large_width()183     fn test_integer_new_panics_on_large_width() {
184         Integer::new(65);
185     }
186 }
187