use std::collections::HashMap;
use itertools::Itertools;
use proc_macro2::TokenStream;
use quote::quote;
use quote::{ToTokens, TokenStreamExt};
use syn::{Path, Type};
use crate::selector::QuerySelector;
#[derive(Debug, Clone, Default)]
pub struct Params(pub Vec<(String, syn::Type)>);
#[derive(Debug, Clone)]
pub struct With(pub String, pub QueryVar);
#[derive(Debug, Clone)]
pub enum QueryVar {
Var(String),
Call(Path, HashMap<String, QueryVar>),
}
#[derive(Debug)]
pub struct Query {
pub params: Params,
pub withs: Vec<With>,
pub result: QuerySelector,
}
impl QueryVar {
pub fn as_simple_name_or_ref(&self) -> Option<&str> {
let QueryVar::Var(s) = self else {
return None;
};
if !s
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
{
return None;
}
Some(s)
}
}
impl ToTokens for Params {
fn to_tokens(&self, tokens: &mut TokenStream) {
for (name, ty) in self.0.iter() {
tokens.append_all(quote! {
fmt.write_fmt(format_args!(
"\t{} := <{}>{},\n",
#name,
<#ty as ::edgedb_composable_query::EdgedbPrim>::TYPE_CAST,
args[#name]
))?;
})
}
}
}
impl ToTokens for QueryVar {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
QueryVar::Var(s) => tokens.append_all(quote! {
#s
}),
QueryVar::Call(strct, args) => {
let args_kv = args
.iter()
.map(|(k, v)| {
quote! {
(#k, format!("({})", #v))
}
})
.collect_vec();
tokens.append_all(quote! {
{
let args = [#( #args_kv ),*].into();
let mut buf = String::new();
<#strct as ::edgedb_composable_query::EdgedbComposableQuery>::format_query(&mut buf, &args)?;
::edgedb_composable_query::__query_add_indent(&buf)
}
})
}
}
}
}
impl ToTokens for With {
fn to_tokens(&self, tokens: &mut TokenStream) {
let With(name, value) = self;
tokens.append_all(quote! {
fmt.write_fmt(format_args!("\t{} := ({}),\n", #name, #value))?;
})
}
}
impl ToTokens for Query {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut inner = TokenStream::new();
if !self.params.0.is_empty() || !self.withs.is_empty() {
inner.append_all(quote! {
fmt.write_str("with\n")?;
});
self.params.to_tokens(&mut inner);
for with in &self.withs {
with.to_tokens(&mut inner);
}
}
let (argnames, argtypes) = self
.params
.0
.iter()
.cloned()
.unzip::<_, _, Vec<String>, Vec<Type>>();
let self_type = quote! {Self};
let (final_selector, final_type) = match &self.result {
QuerySelector::Selector(what, _) => (quote! {format!("select ({})", #what)}, self_type),
QuerySelector::Object(_) => (quote! {"select "}, self_type),
QuerySelector::Direct(what, _ty) => {
(quote! {format!("select ({})", #what)}, quote! {#_ty})
}
};
let atypes = if argtypes.len() == 0 {
quote! {()}
} else {
quote! {(#( #argtypes ),* ,)}
};
tokens.append_all(quote! {
const ARG_NAMES: &'static [&'static str] = &[#( #argnames ),*];
type ArgTypes = #atypes;
type ReturnType = #final_type;
fn format_query(
fmt: &mut impl ::std::fmt::Write,
args: &::std::collections::HashMap<&str, String>
) -> Result<(), ::std::fmt::Error> {
use ::edgedb_composable_query::__itertools::Itertools;
use ::edgedb_composable_query::composable::EdgedbComposableSelector;
#inner
fmt.write_str(&#final_selector)?;
fmt.write_str(" {\n")?;
<#final_type as EdgedbComposableSelector>::format_selector(fmt)?;
fmt.write_str("\n}")?;
Ok(())
}
})
}
}