use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{ItemFn, LitStr}; use crate::common::extract::FnArgument; use super::extract::{extract_components, Components}; pub(crate) struct Expanded { pub(crate) name_str: LitStr, pub(crate) validator: TokenStream, pub(crate) validator_name: Ident, pub(crate) evaluator: TokenStream, pub(crate) evaluator_name: Ident, pub(crate) result: TokenStream, pub(crate) core: TokenStream, } /// Expand the macro input to produce the common elements used in the `#[function]` and /// `#[register]` macros. pub(crate) fn expand(input: ItemFn, name_str: Option) -> Result { let ItemFn { attrs: _, vis: _, sig, block, } = input; let Components { name, generics, validator_name, evaluator_name, result, args, ret, inputs, } = match extract_components(sig) { Ok(fd) => fd, Err(e) => return Err(e.into_compile_error()), }; // Stringified name of the function: let name_str = name_str.unwrap_or_else(|| LitStr::new(name.to_string().as_str(), name.span())); // The number of arguments the function accepts: let args_len = args.len(); // Generate token streams for some needed types: let lazy = quote! { std::sync::LazyLock }; let core = quote! { ::serde_json_path_macros::serde_json_path_core::spec::functions }; let res = quote! { std::result::Result }; // Generate code for checking each individual argument in a query at parse time: let arg_checks = args.iter().enumerate().map(|(idx, arg)| { let FnArgument { ident: _, ty } = arg; quote! { match a[#idx].as_type_kind() { #res::Ok(tk) => { if !tk.converts_to(#ty::json_path_type()) { return #res::Err(#core::FunctionValidationError::MismatchTypeKind { name: String::from(#name_str), expected: #ty::json_path_type(), received: tk, position: #idx, }); } }, #res::Err(err) => return #res::Err(err) } } }); // Generate the validator function used at parse time to validate a function declaration: let validator = quote! { static #validator_name: #core::Validator = #lazy::new(|| { std::boxed::Box::new(|a: &[#core::FunctionExprArg]| { if a.len() != #args_len { return #res::Err(#core::FunctionValidationError::NumberOfArgsMismatch { expected: a.len(), received: #args_len, }); } #(#arg_checks)* Ok(()) }) }); }; // Generate the code to declare each individual argument for evaluation, at query time: let arg_declarations = args.iter().map(|arg| { let FnArgument { ident, ty } = arg; // validation should ensure unwrap is okay here: quote! { let #ident = #ty::try_from(v.pop_front().unwrap()).unwrap(); } }); // Produce the argument name identifiers: let arg_names = args.iter().map(|arg| { let FnArgument { ident, ty: _ } = arg; ident }); // Generate the evaluator function used to evaluate a function at query time: let evaluator = quote! { fn #name #generics (#inputs) #ret #block static #evaluator_name: #core::Evaluator = #lazy::new(|| { std::boxed::Box::new(|mut v: std::collections::VecDeque<#core::JsonPathValue>| { #(#arg_declarations)* return #name(#(#arg_names,)*).into() }) }); }; Ok(Expanded { name_str, validator, validator_name, evaluator, evaluator_name, result, core, }) }