summaryrefslogtreecommitdiffstats
path: root/vendor/varisat-internal-macros/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/varisat-internal-macros/src/lib.rs')
-rw-r--r--vendor/varisat-internal-macros/src/lib.rs216
1 files changed, 216 insertions, 0 deletions
diff --git a/vendor/varisat-internal-macros/src/lib.rs b/vendor/varisat-internal-macros/src/lib.rs
new file mode 100644
index 000000000..f6bfc8f25
--- /dev/null
+++ b/vendor/varisat-internal-macros/src/lib.rs
@@ -0,0 +1,216 @@
+//! Internal macros for the Varisat SAT solver.
+#![recursion_limit = "128"]
+use std::fmt::Write;
+
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{
+ parse_quote, punctuated::Punctuated, Attribute, Fields, Ident, Lit, LitStr, Meta,
+ MetaNameValue, Token,
+};
+use synstructure::decl_derive;
+
+/// Get the doc comment as LitStr from the attributes
+fn doc_from_attrs(attrs: &[Attribute]) -> Vec<LitStr> {
+ let mut lines = vec![];
+
+ for attr in attrs.iter() {
+ if let Ok(Meta::NameValue(MetaNameValue {
+ path,
+ lit: Lit::Str(doc_str),
+ ..
+ })) = attr.parse_meta()
+ {
+ if let Some(ident) = path.get_ident() {
+ if ident == "doc" {
+ lines.push(doc_str);
+ }
+ }
+ }
+ }
+
+ lines
+}
+
+/// Find a field inside the doc comment
+fn get_doc_field(name: &str, attrs: &[Attribute]) -> Option<LitStr> {
+ let re = regex::Regex::new(&format!(r"\[{}: (.+?)\]( |$)", regex::escape(name))).unwrap();
+
+ for doc_str in doc_from_attrs(attrs) {
+ if let Some(expr_str) = re.captures(&doc_str.value()) {
+ let expr_str = expr_str.get(1).unwrap().as_str();
+ let expr_str = LitStr::new(expr_str, doc_str.span());
+ return Some(expr_str);
+ }
+ }
+
+ None
+}
+
+/// Derives a default instance from the documentation.
+fn derive_doc_default(s: synstructure::Structure) -> TokenStream {
+ let variant = match s.variants() {
+ [variant] => variant,
+ _ => panic!("DocDefault requires a struct"),
+ };
+
+ let body = variant.construct(|field, _| {
+ get_doc_field("default", &field.attrs)
+ .map(|expr_str| {
+ expr_str
+ .parse::<TokenStream>()
+ .expect("error parsing default expression")
+ })
+ .unwrap_or_else(|| parse_quote!(Default::default()))
+ });
+
+ s.gen_impl(quote! {
+ gen impl Default for @Self {
+ fn default() -> Self {
+ #body
+ }
+ }
+ })
+}
+
+decl_derive!([DocDefault] => derive_doc_default);
+
+/// Derives an update struct and method for a config struct.
+fn derive_config_update(s: synstructure::Structure) -> TokenStream {
+ let variant = match s.variants() {
+ [variant] => variant,
+ _ => panic!("ConfigUpdate requires a struct"),
+ };
+
+ let fields = match variant.ast().fields {
+ Fields::Named(fields_named) => &fields_named.named,
+ _ => panic!("ConfigUpdate requires named fields"),
+ };
+
+ assert!(
+ s.referenced_ty_params().is_empty(),
+ "ConfigUpdate doesn't support type parameters"
+ );
+
+ let ident = &s.ast().ident;
+ let update_struct_ident = Ident::new(&format!("{}Update", ident), ident.span());
+
+ let vis = &s.ast().vis;
+
+ let update_struct_body = fields
+ .iter()
+ .map(|field| {
+ let ty = &field.ty;
+ let mut field = field.clone();
+ field.ty = parse_quote!(Option<#ty>);
+ field
+ })
+ .collect::<Punctuated<_, Token![,]>>();
+
+ let check_ranges = fields
+ .iter()
+ .map(|field| {
+ if let Some(range) = get_doc_field("range", &field.attrs) {
+ // TODO use toml instead of fmt::Debug for errors?
+ let ident = &field.ident;
+ let error_msg = format!(
+ "{} must be in range {} but was set to {{:?}}",
+ quote!(#ident),
+ range.value()
+ );
+ let range = range
+ .parse::<TokenStream>()
+ .expect("error parsing range expression");
+ quote! {
+ if let Some(value) = &self.#ident {
+ anyhow::ensure!((#range).contains(value), #error_msg, value);
+ }
+ }
+ } else {
+ quote!()
+ }
+ })
+ .collect::<TokenStream>();
+
+ let apply_updates = fields
+ .iter()
+ .map(|field| {
+ let ident = &field.ident;
+ quote! {
+ if let Some(value) = &self.#ident {
+ config.#ident = value.clone();
+ }
+ }
+ })
+ .collect::<TokenStream>();
+
+ let merge_updates = fields
+ .iter()
+ .map(|field| {
+ let ident = &field.ident;
+ quote! {
+ if let Some(value) = config_update.#ident {
+ self.#ident = Some(value);
+ }
+ }
+ })
+ .collect::<TokenStream>();
+
+ let mut help_str = String::new();
+
+ for field in fields.iter() {
+ let ident = &field.ident;
+ writeln!(&mut help_str, "{}:", quote!(#ident)).unwrap();
+ for line in doc_from_attrs(&field.attrs).iter() {
+ if line.value().is_empty() {
+ writeln!(&mut help_str).unwrap();
+ } else {
+ writeln!(&mut help_str, " {}", line.value()).unwrap();
+ }
+ }
+ writeln!(&mut help_str).unwrap();
+ }
+
+ let doc = format!("Updates configuration values of [`{}`].", ident);
+
+ quote! {
+ #[doc = #doc]
+ #[derive(Default, serde::Serialize, serde::Deserialize)]
+ #[serde(deny_unknown_fields)]
+ #vis struct #update_struct_ident {
+ #update_struct_body
+ }
+
+ impl #ident {
+ /// Return a string describing all supported configuration options.
+ pub fn help() -> &'static str {
+ #help_str
+ }
+ }
+
+ impl #update_struct_ident {
+ /// Create an empty config update.
+ pub fn new() -> #update_struct_ident {
+ #update_struct_ident::default()
+ }
+
+ /// Apply the configuration update.
+ ///
+ /// If an error occurs, the configuration is not changed.
+ pub fn apply(&self, config: &mut #ident) -> Result<(), anyhow::Error> {
+ #check_ranges
+ #apply_updates
+ Ok(())
+ }
+
+ /// Merge two configuration updates.
+ ///
+ /// Add the given update, overwriting values of the receiving update.
+ pub fn merge(&mut self, config_update: #update_struct_ident) {
+ #merge_updates
+ }
+ }
+ }
+}
+
+decl_derive!([ConfigUpdate] => derive_config_update);