Files
windmill/backend/windmill-macros/src/lib.rs
T
pyranota c3ba832444 Annotations refactor (#4512)
* Refactor annotation system

* Move tests to different location

* Limit annotations per line (15)

* Write initial benchmark

* Optimize

* More optimizations

* Rewrite with 3x speed!

* Remove benchmarks

They were in wrong place and for development

* Polishing

* Remove unused leftover

* Integrate new annotation system

* Add comment explanation

* Remove `hashe-based matching`

Turned to be it is impure and non-deterministic method

* Add `tracing::error` for `// native` without `deno_core`

If you build backend without `deno_core` feature flag, and add //native
annotation to ts script, it would just silently exit.

This commit prints error to logs

* add more tests

* Update annotations.rs

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-10-17 09:14:31 +02:00

96 lines
3.2 KiB
Rust

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Ident, ItemStruct, Lit};
#[proc_macro_attribute]
pub fn annotations(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemStruct);
let name = input.ident.clone();
let fields = input
.fields
.iter()
.map(|f| f.ident.clone().unwrap())
.collect::<Vec<Ident>>();
// Match on the literal to extract the string value
let comm_lit = match parse_macro_input!(attr as Lit) {
Lit::Str(lit_str) => lit_str.value(), // This will give "#" without quotes
_ => panic!("Expected a string literal"),
};
// Generate regex
let mut reg = format!("^{}|", &comm_lit);
{
for field in fields.iter() {
reg.push_str(&(field.to_string()));
reg.push_str("\\b");
}
reg.push_str(r#"|\w+"#);
}
// Example of generated regex:
// ^#
// |ann1\b|ann2\b|ann3\b|ann4\b
// |\w+
TokenStream::from(quote! {
#[derive(Default, Debug)]
#input
impl std::ops::BitOrAssign for #name{
fn bitor_assign(&mut self, rhs: Self) {
// Unfold fields
// Read more: https://docs.rs/quote/latest/quote/macro.quote.html#interpolation
#( self.#fields |= rhs.#fields; )*
}
}
impl #name {
/// Autogenerated by windmill-macros
pub fn parse(inner_content: &str) -> Self{
let mut res = Self::default();
lazy_static::lazy_static! {
static ref RE: regex::Regex = regex::Regex::new(#reg).unwrap();
}
// Create lines stream
let mut lines = inner_content.lines();
'outer: while let Some(line) = lines.next() {
// If comment sign(s) on the right place
let mut comms = false;
// New instance
// We will apply it if in line only annotations
let mut new = Self::default();
'inner: for (i, mat) in RE.find_iter(line).enumerate() {
match mat.as_str(){
#comm_lit if i == 0 => {
comms = true;
continue 'inner;
},
// Will expand into something like:
// "ann1" => new.ann1 = true,
// "ann2" => new.ann2 = true,
// "ann3" => new.ann3 = true,
#( stringify!(#fields) => new.#fields = true, )*
// Non annotations
_ => continue 'outer,
};
}
if !comms {
// We dont want to continue if line does not start with #
return res;
}
// Apply changes
res |= new;
}
res
}
}
})
}