typst_wasm_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote, ToTokens};
3use syn::{parse::Parser, Expr};
4use venial::*;
5
6fn construct_export(
7    name: &proc_macro2::Ident,
8    vis_marker: Option<VisMarker>,
9    params: Vec<FnTypedParam>,
10    export_rename: &Option<Expr>,
11) -> proc_macro2::TokenStream {
12    fn param_idx(name: &proc_macro2::Ident) -> proc_macro2::Ident {
13        format_ident!("__{}_idx", name)
14    }
15
16    let inner_name = format_ident!("__wasm_minimal_protocol_internal_function_{}", name);
17    // let export_name = proc_macro2::Literal::string(&name.to_string());
18    let export_name = export_rename
19        .as_ref()
20        .unwrap_or(&Expr::Lit(syn::ExprLit {
21            attrs: vec![],
22            lit: syn::Lit::Str(syn::LitStr::new(&name.to_string(), name.span())),
23        }))
24        .to_token_stream();
25    let p_idx = params
26        .iter()
27        .map(|p| &p.name)
28        .map(param_idx)
29        .collect::<Vec<_>>();
30    let param_names = params.iter().map(|p| p.name.clone()).collect::<Vec<_>>();
31
32    let get_unsplit_params = if params.len() == 0 {
33        quote!()
34    } else {
35        quote!(
36            let __total_len = #(#p_idx + )* 0;
37            let mut __unsplit_params = vec![0u8; __total_len];
38            typst_wasm_protocol::write_args_to_buffer(__unsplit_params.as_mut_ptr());
39        )
40    };
41
42    let mut set_args = quote!(
43        let mut start: usize = 0;
44    );
45    for param in params.iter() {
46        let name = &param.name;
47        let ty = &param.ty;
48        let idx = param_idx(name);
49        set_args.extend(quote!(
50            let #name: #ty = (&__unsplit_params[start..start + #idx]).into();
51            start += #idx;
52        ));
53    }
54
55    quote!(
56        #[export_name = #export_name]
57        #vis_marker extern "C" fn #inner_name(#(#p_idx: usize),*) -> i32 {
58            #get_unsplit_params
59            #set_args
60
61            let result = #name(#(#param_names),*);
62            typst_wasm_protocol::PluginResult::send_result(result)
63        }
64    )
65    .into()
66}
67
68struct MacroOptions {
69    pub export_rename: Option<Expr>,
70}
71
72#[proc_macro_attribute]
73pub fn wasm_export(args: TokenStream, item: TokenStream) -> TokenStream {
74    let args = syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated
75        .parse(args)
76        .expect("invalid arguments");
77
78    let mut options = MacroOptions {
79        export_rename: None,
80    };
81    for arg in args {
82        let syn::MetaNameValue { path, value, .. } = arg;
83        match path.get_ident().expect("invalid argument") {
84            x if x == "export_rename" => {
85                options.export_rename = Some(value);
86            }
87            _ => panic!("invalid argument"),
88        }
89    }
90
91    let mut item = proc_macro2::TokenStream::from(item);
92    let decl = parse_item(item.clone()).expect("invalid declaration");
93    let func = match decl.as_function() {
94        Some(func) => func.clone(),
95        None => {
96            let error = venial::Error::new_at_tokens(
97                &item,
98                "#[wasm_export] can only be applied to a function",
99            );
100            item.extend(error.to_compile_error());
101            return item.into();
102        }
103    };
104    let Function {
105        name,
106        params,
107        vis_marker,
108        ..
109    } = func.clone();
110
111    let mut error = None;
112    let p = params
113        .items()
114        .filter_map(|x| match x {
115            FnParam::Receiver(_p) => {
116                let x = x.to_token_stream();
117                error = Some(venial::Error::new_at_tokens(
118                    &x,
119                    format!("the {x} argument is not allowed by the protocol"),
120                ));
121                None
122            }
123            FnParam::Typed(p) => Some(p.clone()),
124        })
125        .collect::<Vec<_>>();
126
127    let mut result = quote!(#func);
128    if let Some(error) = error {
129        result.extend(error.to_compile_error());
130    } else {
131        result.extend(construct_export(
132            &name,
133            vis_marker,
134            p,
135            &options.export_rename,
136        ));
137    }
138    result.into()
139}