1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use crate::{
code_gen::{helpers::wrap_in_optional, parsely_common_field_data::ParselyCommonFieldData},
model_types::CollectionLimit,
syn_helpers::MemberExts,
ParselyReadFieldReceiver, TypeExts,
};
use super::helpers::{
generate_collection_read, generate_plain_read, wrap_read_with_padding_handling,
};
/// A struct which represents all information needed for generating logic to read a field from a
/// buffer.
#[derive(Debug)]
pub struct ParselyReadFieldData {
/// Data common between read and write for fields
pub(crate) common: ParselyCommonFieldData,
/// Required when there's a collection field
pub(crate) collection_limit: Option<CollectionLimit>,
/// Instead of reading the value of this field from the buffer, assign it from the given
/// [`syn::Ident`]
pub(crate) assign_from: Option<syn::Expr>,
/// 'when' is required when there's an optional field
pub(crate) when: Option<syn::Expr>,
}
impl ParselyReadFieldData {
pub(crate) fn from_receiver(
field_ident: syn::Member,
receiver: ParselyReadFieldReceiver,
) -> Self {
let collection_limit = if receiver.ty.is_collection() {
if let Some(count) = receiver.count {
Some(CollectionLimit::Count(count))
} else if let Some(while_pred) = receiver.while_pred {
Some(CollectionLimit::While(while_pred))
} else {
panic!("Collection type must have 'count' or 'while' attribute");
}
} else {
None
};
let when = if receiver.ty.is_option() {
Some(
receiver
.when
.expect("Option field must have 'when' attribute"),
)
} else {
None
};
let common = ParselyCommonFieldData {
ident: field_ident,
ty: receiver.ty,
assertion: receiver.common.assertion,
context: receiver.common.context,
map: receiver.common.map,
alignment: receiver.common.alignment,
};
Self {
common,
collection_limit,
assign_from: receiver.assign_from,
when,
}
}
}
impl ToTokens for ParselyReadFieldData {
/// Given the data associated with a field, generate the code for properly reading it from a
/// buffer.
///
/// The attributes set in the [`ParselyReadFieldData`] all shape the logic necessary in order to
/// properly parse this field. Roughly, the processing is as follows:
///
/// 1. Check if an 'assign_from' attribute is set. If so, we don't read from the buffer at all and
/// instead just assign the field to the result of the given expression.
/// 2. Check if a 'map' attribute is set. If so, we'll read a value as a different type and then
/// pass it t othe map function to arrive at the final type and assign it to the field.
/// 3. Check if the field is a collection. If so, some kind of accompanying 'limit' attribute is
/// required: either a 'count' attribute or a `while_pred` attribute that defines how many
/// elements should be read.
/// 4. If none of the above are the case, do a 'plain' read where we just read the type directly
/// from the buffer.
/// 5. If an 'assertion' attribute is present then generate code to assert on the read value using
/// the given assertion function or closure.
/// 6. After the code to perform the read has been generated, we check if the field is an option
/// type. If so, a 'when' attribute is required. This is an expression that determines when the
/// read should actually be done.
/// 7. Finally, if an 'alignment' attribute is present, code is added to detect and consume any
/// padding after the read.
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut output = TokenStream::new();
if let Some(ref assign_expr) = self.assign_from {
output.extend(quote! {
ParselyResult::<_>::Ok(#assign_expr)
});
} else if let Some(ref map_expr) = self.common.map {
map_expr.to_read_map_tokens(&self.common.ident, &mut output);
} else if self.common.ty.is_collection() {
// We've ensure collection_limit is set in this case elswhere.
let limit = self.collection_limit.as_ref().unwrap();
let read_type = self.common.buffer_type();
output.extend(generate_collection_read(
limit,
read_type,
&self.common.context_values(),
));
} else {
output.extend(generate_plain_read(
self.common.buffer_type(),
&self.common.context_values(),
));
}
if let Some(ref assertion) = self.common.assertion {
assertion
.to_read_assertion_tokens(&self.common.ident.as_friendly_string(), &mut output);
}
let error_context = format!("Reading field '{}'", self.common.ident.as_friendly_string());
output.extend(quote! {
.with_context(|| #error_context)?
});
output = if self.common.ty.is_option() && self.common.map.is_none() {
// We've ensured 'when' is set in this case elsehwere
let when_expr = self.when.as_ref().unwrap();
wrap_in_optional(when_expr, output)
} else {
output
};
output = if let Some(alignment) = self.common.alignment {
wrap_read_with_padding_handling(&self.common.ident, alignment, output)
} else {
output
};
let field_variable_name = self.common.ident.as_variable_name();
tokens.extend(quote! {
let #field_variable_name = #output;
})
}
}