opendp_tooling/bootstrap/
arguments.rs1use darling::{Error, FromMeta, Result, ast::NestedMeta};
2use proc_macro2::TokenStream;
3use std::{collections::HashMap, str::FromStr};
4use syn::{Expr, ExprLit, Lit, Meta, MetaNameValue, Token, Type, punctuated::Punctuated};
5
6use crate::{TypeRecipe, Value};
7
8use super::signature::{syn_path_to_string, syn_type_to_type_recipe};
9
10#[derive(Debug, FromMeta, Clone)]
13pub struct BootstrapArguments {
14 pub name: Option<String>,
15 pub proof_path: Option<String>,
16 #[darling(default)]
17 pub rust_path: Option<String>,
18 #[darling(default)]
19 pub has_ffi: Option<bool>,
20 #[darling(default)]
21 pub unproven: bool,
22 #[darling(default)]
23 pub features: Features,
24 #[darling(default)]
25 pub generics: BootTypeHashMap,
26 #[darling(default)]
27 pub arguments: BootTypeHashMap,
28 pub derived_types: Option<DerivedTypes>,
29 pub returns: Option<BootType>,
30}
31
32impl BootstrapArguments {
33 pub fn from_attribute_args(items: &[NestedMeta]) -> darling::Result<Self> {
34 Self::from_list(items)
35 }
36}
37
38#[derive(Debug, Clone, Default)]
39pub struct DerivedTypes(pub Vec<(String, TypeRecipe)>);
40
41impl FromMeta for DerivedTypes {
42 fn from_list(items: &[NestedMeta]) -> Result<Self> {
43 (items.iter())
46 .map(|nested| {
47 let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, value, .. })) = nested
48 else {
49 return Err(
50 Error::custom("expected name = value pairs in derived_types")
51 .with_span(nested),
52 );
53 };
54 let Expr::Lit(ExprLit { lit, .. }) = value else {
55 return Err(Error::custom("expected literal").with_span(value));
56 };
57 Ok((syn_path_to_string(path)?, TypeRecipe::from_value(lit)?))
58 })
59 .collect::<Result<Vec<(String, TypeRecipe)>>>()
60 .map(DerivedTypes)
61 }
62}
63
64#[derive(Debug, Clone, Default)]
65pub struct Features(pub Vec<String>);
66
67impl FromMeta for Features {
68 fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
69 items
70 .iter()
71 .map(String::from_nested_meta)
72 .collect::<darling::Result<Vec<String>>>()
73 .map(Features)
74 }
75}
76
77#[derive(Debug, Clone, Default)]
78pub struct BootTypeHashMap(pub HashMap<String, BootType>);
79
80impl FromMeta for BootTypeHashMap {
81 fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
82 (items.iter())
83 .map(|nested| {
84 if let NestedMeta::Meta(Meta::List(list)) = nested {
85 let type_name = list
86 .path
87 .get_ident()
88 .ok_or_else(|| {
89 darling::Error::custom("path must consist of a single ident")
90 .with_span(&list.path)
91 })?
92 .to_string();
93
94 let type_ = BootType::from_list(
95 &list
96 .parse_args_with(Punctuated::<NestedMeta, Token![,]>::parse_terminated)?
97 .into_iter()
98 .collect::<Vec<_>>(),
99 )?;
100 Ok((type_name, type_))
101 } else {
102 Err(
103 darling::Error::custom("expected metalist in BootstrapTypes")
104 .with_span(nested),
105 )
106 }
107 })
108 .collect::<darling::Result<HashMap<String, BootType>>>()
109 .map(BootTypeHashMap)
110 }
111}
112
113#[derive(Debug, FromMeta, Clone, Default)]
114pub struct BootType {
115 pub c_type: Option<String>,
116 pub rust_type: Option<TypeRecipe>,
117 pub hint: Option<String>,
118 pub default: Option<Value>,
119 #[darling(default)]
120 pub do_not_convert: bool,
121 pub example: Option<TypeRecipe>,
122 #[darling(default)]
123 pub suppress: bool,
124}
125
126impl FromMeta for Value {
127 fn from_expr(expr: &Expr) -> Result<Self> {
128 match *expr {
129 Expr::Unary(ref unary) => {
130 if !matches!(unary.op, syn::UnOp::Neg(_)) {
131 return Err(
132 Error::custom("the only unary operation supported is negation")
133 .with_span(expr),
134 );
135 }
136 Ok(match Self::from_expr(&unary.expr)? {
137 Value::Integer(v) => Value::Integer(-v),
138 Value::Float(v) => Value::Float(-v),
139 v => v,
140 })
141 }
142 Expr::Lit(ref lit) => Self::from_value(&lit.lit),
143 Expr::Group(ref group) => {
144 Self::from_expr(&group.expr)
150 }
151 _ => Err(Error::unexpected_expr_type(expr)),
152 }
153 .map_err(|e| e.with_span(expr))
154 }
155
156 fn from_value(value: &syn::Lit) -> darling::Result<Self> {
157 Ok(match value {
158 syn::Lit::Str(str) => Value::String(str.value()),
159 syn::Lit::Int(int) => Value::Integer(int.base10_parse::<i64>()?),
160 syn::Lit::Float(float) => Value::Float(float.base10_parse::<f64>()?),
161 syn::Lit::Bool(bool) => Value::Bool(bool.value),
162 syn::Lit::ByteStr(bstr) => {
163 if bstr.value() == b"null" {
164 Value::Null
165 } else {
166 return Err(Error::custom("Byte strings are reserved for expressing nullity. Use b\"null\" to represent a null literal.").with_span(bstr));
167 }
168 }
169 lit => return Err(darling::Error::unexpected_lit_type(lit).with_span(value)),
170 })
171 }
172}
173
174impl FromMeta for TypeRecipe {
175 fn from_value(value: &Lit) -> Result<Self> {
176 match value {
177 Lit::Str(litstr) => {
178 let litstr = litstr.value();
179 if litstr.starts_with('$') {
180 let mut chars = litstr.chars();
181 chars.next(); let stream = TokenStream::from_str(chars.as_str()).map_err(|_| {
183 Error::custom("error while lexing function").with_span(value)
184 })?;
185
186 syn_meta_to_type_recipe_function(syn::parse2::<NestedMeta>(stream)?)
187 } else {
188 let stream = TokenStream::from_str(&litstr)
189 .map_err(|_| Error::custom("error while lexing type").with_span(value))?;
190
191 let ty = syn::parse2::<Type>(stream).map_err(|e| {
192 Error::custom(format!(
193 "error while parsing type {}: {}",
194 litstr,
195 e.to_string()
196 ))
197 .with_span(value)
198 })?;
199 syn_type_to_type_recipe(&ty)
200 }
201 }
202 Lit::ByteStr(bstr) => {
203 if bstr.value() == b"null" {
204 Ok(TypeRecipe::None)
205 } else {
206 Err(Error::custom("Byte strings are reserved for expressing nullity. Use b\"null\" to represent a null literal.").with_span(bstr))
207 }
208 }
209 _ => Err(Error::custom("expected string").with_span(value)),
210 }
211 }
212}
213
214fn syn_meta_to_type_recipe_function(meta: NestedMeta) -> Result<TypeRecipe> {
215 match meta {
216 NestedMeta::Meta(Meta::Path(path)) => syn_path_to_string(&path).map(TypeRecipe::Name),
217 NestedMeta::Meta(Meta::List(list)) => Ok(TypeRecipe::Function {
218 function: syn_path_to_string(&list.path)?,
219 params: list
220 .parse_args_with(Punctuated::<NestedMeta, Token![,]>::parse_terminated)?
221 .into_iter()
222 .map(syn_meta_to_type_recipe_function)
223 .collect::<Result<Vec<TypeRecipe>>>()?,
224 }),
225 meta => Err(Error::custom(
226 "rust_type = \"$<expression>\" only supports a limited function grammar",
227 )
228 .with_span(&meta)),
229 }
230}
231
232#[derive(Debug, Default, Clone)]
233pub struct DefaultGenerics(pub Vec<String>);
234
235impl FromMeta for DefaultGenerics {
236 fn from_value(lit: &Lit) -> Result<Self> {
237 if let Lit::Str(litstr) = lit {
238 Ok(DefaultGenerics(
239 litstr
240 .value()
241 .split(',')
242 .map(|v| v.trim().to_string())
243 .collect(),
244 ))
245 } else {
246 Err(Error::custom("expected string").with_span(lit))
247 }
248 }
249}