cido_ethereum_macros/
lib.rs

1use proc_macro2::{Span, TokenStream};
2use std::path::Path;
3
4mod cido_meta;
5mod contract;
6pub(crate) mod types;
7mod util;
8
9/// Proc macro for creating types and functions to interact with a contract from a json abi file
10#[proc_macro_attribute]
11pub fn ethereum_contract(
12  args: proc_macro::TokenStream,
13  input: proc_macro::TokenStream,
14) -> proc_macro::TokenStream {
15  crate::contract::RawContract::from_input(args.into(), input.into())
16    .and_then(|rc| rc.derived())
17    .unwrap_or_else(|e| e.into_compile_error())
18    .into()
19}
20
21// These are all duplicated from cido-macros so if we ever have a common crate, use those.
22pub(crate) fn err(span: &dyn syn::spanned::Spanned, t: impl core::fmt::Display) -> syn::Error {
23  syn::Error::new(span.span(), t)
24}
25
26pub(crate) fn ensure_generics_zero(generics: &syn::Generics) -> deluxe::Result<()> {
27  if generics.const_params().next().is_some()
28    || generics.lifetimes().next().is_some()
29    || generics.type_params().next().is_some()
30  {
31    return Err(deluxe::Error::new(
32      Span::call_site(),
33      "Unable to handle generics",
34    ));
35  }
36  Ok(())
37}
38
39pub(crate) fn embed_generated_code(
40  embed: bool,
41  name: &syn::Ident,
42  output: proc_macro2::TokenStream,
43  macro_out: &str,
44) -> Result<proc_macro2::TokenStream, syn::Error> {
45  let embed = embed
46    || std::env::var("CIDO_ETHEREUM_EMBED_GENERATED_CODE")
47      .map_or(false, |s| s == "1" || s == "true");
48  if embed {
49    let out_dir = std::env::var("OUT_DIR")
50      .or_else(|_| std::env::var("TMPDIR"))
51      .unwrap_or_else(|_| "/tmp".to_string());
52    let out_file = format!("{out_dir}/{name}.{macro_out}.rs");
53    println!("Including {name} instead of generating in {out_file}");
54    write_to_file(out_file.as_ref(), &output).map_err(|e| {
55      syn::Error::new(
56        name.span(),
57        format_args!("Unable to write output to file {out_file}. {e}"),
58      )
59    })?;
60    Ok(quote::quote!(::std::include! {#out_file}))
61  } else {
62    Ok(output)
63  }
64}
65
66/// # Errors
67/// Returns an error if the tokens can't be parsed into a `syn::File` or if writing to the file path fails
68pub(crate) fn write_to_file(path: &Path, tokens: &TokenStream) -> syn::Result<()> {
69  let content = tokens.to_string();
70
71  let code = match syn::parse_file(&content) {
72    Ok(output) => {
73      std::panic::catch_unwind(|| prettyplease::unparse(&output)).unwrap_or_else(|panic| {
74        if let Some(s) = panic.downcast_ref::<String>() {
75          eprintln!("prettyplease::unparse panicked. Using raw output. Panic message: {s}");
76        } else {
77          eprintln!("prettyplease::unparse panicked. Using raw output");
78        }
79        content
80      })
81    }
82    Err(e) => {
83      eprintln!("syntax error found. Using raw token stream. Error = {e}");
84      content
85    }
86  };
87
88  write_string_to_file(tokens, path, &code)
89}
90
91/// # Errors
92/// Returns an error if writing to the file path fails
93pub(crate) fn write_string_to_file(
94  span: &dyn syn::spanned::Spanned,
95  path: &Path,
96  content: &str,
97) -> syn::Result<()> {
98  use std::io::Write;
99  let display_path = path.display();
100
101  match std::fs::read_to_string(path) {
102    // don't touch the file if nothing has changed to prevent unnecessary rebuilds
103    Ok(existing_code) if existing_code == content => {}
104    _ => {
105      println!("writing to {display_path}");
106      let mut file = std::fs::File::create(path).map_err(|e| {
107        eprintln!("Unable to create file {display_path}. {e}");
108        syn::Error::new(span.span(), e)
109      })?;
110      file.write_all(content.as_bytes()).map_err(|e| {
111        eprintln!("Unable to write to file {display_path}. {e}");
112        syn::Error::new(span.span(), e)
113      })?;
114      file.sync_all().map_err(|e| {
115        eprintln!("Unable to sync file {display_path}. {e}");
116        syn::Error::new(span.span(), e)
117      })?;
118    }
119  }
120  Ok(())
121}
122
123#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Default)]
124#[repr(u8)]
125enum ProcessingOrder {
126  #[default]
127  One = 1,
128  Two = 2,
129  Three = 3,
130}
131
132impl From<ProcessingOrder> for u8 {
133  fn from(val: ProcessingOrder) -> Self {
134    val as u8
135  }
136}
137
138impl TryFrom<u8> for ProcessingOrder {
139  type Error = u8;
140
141  fn try_from(value: u8) -> Result<Self, Self::Error> {
142    match value {
143      1 => Ok(ProcessingOrder::One),
144      2 => Ok(ProcessingOrder::Two),
145      3 => Ok(ProcessingOrder::Three),
146      val => Err(val),
147    }
148  }
149}
150
151impl deluxe::ParseMetaItem for ProcessingOrder {
152  fn parse_meta_item(
153    input: syn::parse::ParseStream,
154    _mode: deluxe::ParseMode,
155  ) -> deluxe::Result<Self> {
156    let u = input.parse::<syn::LitInt>()?;
157    let u = u.base10_parse::<u8>()?;
158    Self::try_from(u).map_err(|_| crate::err(&u, "Invalid order"))
159  }
160}