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