extern crate proc_macro;
use proc_macro::{
token_stream::IntoIter, Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream,
TokenTree,
};
enum StmtKind {
Insert,
Update,
Unknown,
}
struct BindSet {
ty: Option<Vec<TokenTree>>,
binding: Ident,
start: usize,
end: usize,
}
struct ColumnRefList {
columns: Vec<Option<Ident>>,
bindsets: Vec<BindSet>,
}
fn chr(ch: char) -> TokenTree {
TokenTree::Punct(Punct::new(ch, Spacing::Alone))
}
fn chrj(ch: char) -> TokenTree {
TokenTree::Punct(Punct::new(ch, Spacing::Joint))
}
fn ident(value: &str, span: Span) -> TokenTree {
TokenTree::Ident(Ident::new(value, span))
}
fn braced(stream: TokenStream) -> TokenTree {
TokenTree::Group(Group::new(Delimiter::Brace, stream))
}
fn take_until_group(stream: &mut IntoIter) -> Option<(Vec<TokenTree>, Group)> {
let mut inside = Vec::<TokenTree>::new();
while let Some(tok) = stream.next() {
if let TokenTree::Group(group) = tok {
return Some((inside, group));
}
inside.push(tok)
}
None
}
impl ColumnRefList {
fn wrap(&self, input: TokenStream) -> TokenTree {
if self.bindsets.is_empty() {
return TokenTree::Group(Group::new(Delimiter::Brace, input));
}
let mut ret = TokenStream::new();
for bindset in &self.bindsets {
let span = bindset.binding.span();
let cols = &self.columns[bindset.start..bindset.end];
if let Some(value) = &bindset.ty {
let mut struct_init = TokenStream::default();
for (i, col) in cols.iter().enumerate() {
if let Some(col) = col {
let mut name = col.to_string();
name.make_ascii_lowercase();
struct_init.extend([
TokenTree::Ident(Ident::new(&name, col.span())),
chr(':'),
TokenTree::Literal(Literal::usize_suffixed(i + bindset.start)),
chr(','),
]);
}
}
ret.extend([
TokenTree::Ident(bindset.binding.clone()),
chr('='),
chr('&'),
]);
ret.extend(value.iter().cloned());
ret.extend([
braced(struct_init),
ident("as", span),
chr('&'),
chrj('\''),
ident("static", span),
]);
ret.extend(value.iter().cloned());
ret.extend([chr(';')]);
continue;
}
let mut struct_definition = TokenStream::default();
for col in cols {
if let Some(col) = col {
let mut name = col.to_string();
name.make_ascii_lowercase();
struct_definition.extend([
TokenTree::Ident(Ident::new(&name, col.span())),
chr(':'),
ident("usize", span),
chr(','),
]);
}
}
let mut struct_init = TokenStream::default();
for (i, col) in cols.iter().enumerate() {
if let Some(col) = col {
let mut name = col.to_string();
name.make_ascii_lowercase();
struct_init.extend([
TokenTree::Ident(Ident::new(&name, col.span())),
chr(':'),
TokenTree::Literal(Literal::usize_suffixed(i + bindset.start)),
chr(','),
]);
}
}
ret.extend([
ident("struct", span),
ident("QueryColumns", span),
braced(struct_definition),
chr(';'),
TokenTree::Ident(bindset.binding.clone()),
chr('='),
chr('&'),
ident("QueryColumns", span),
braced(struct_init),
ident("as", span),
chr('&'),
chrj('\''),
ident("static", span),
ident("QueryColumns", span),
chr(';'),
]);
}
ret.extend(input);
TokenTree::Group(Group::new(Delimiter::Brace, ret))
}
}
struct Source {
builder: Option<Ident>,
text: String,
fragments: Vec<TokenTree>,
captures: Vec<(Span, TokenStream)>,
stmt_kind: StmtKind,
binding: Option<(Ident, Ident)>,
error: Option<(Span, String)>,
ref_list: Option<ColumnRefList>,
select_bindings_depth: u32,
fragment_text_span: Option<(usize, usize)>,
}
#[inline]
fn with_span(mut token: TokenTree, span: Span) -> TokenTree {
token.set_span(span);
token
}
impl Source {
fn add_binding_fragment_appender(&mut self, span: Span, builder: Ident, stream: TokenStream) {
let cs = Span::call_site();
self.fragments.push(TokenTree::from(builder));
self.fragments
.push(TokenTree::Punct(Punct::new('.', Spacing::Joint)));
self.fragments
.push(TokenTree::Ident(Ident::new("push_binding", cs)));
let mut p1 = TokenTree::Punct(Punct::new('&', Spacing::Joint));
p1.set_span(span);
let mut p2 = TokenTree::Group(Group::new(Delimiter::Parenthesis, stream));
p2.set_span(span);
self.fragments.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([p1, p2]),
)));
self.fragments
.push(TokenTree::Punct(Punct::new(';', Spacing::Joint)));
}
pub fn flush_fragments_appender(&mut self, builder: Ident) {
let cs = Span::call_site();
if !self.text.is_empty() {
self.fragments.push(TokenTree::from(builder));
self.fragments
.push(TokenTree::Punct(Punct::new('.', Spacing::Joint)));
self.fragments.push(TokenTree::Ident(Ident::new("raw", cs)));
self.fragments
.push(TokenTree::Punct(Punct::new('.', Spacing::Joint)));
self.fragments
.push(TokenTree::Ident(Ident::new("push_str", cs)));
self.fragments.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from(TokenTree::Literal(Literal::string(&self.text))),
)));
self.fragments
.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
self.text.clear();
}
}
fn add_binding_fragment(&mut self, span: Span, stream: TokenStream) {
if let Some(builder) = &self.builder {
self.add_binding_fragment_appender(span, builder.clone(), stream);
return;
}
let cs = Span::call_site();
self.fragments
.push(TokenTree::from(Ident::new("simple_pg", cs)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
self.fragments
.push(TokenTree::Ident(Ident::new("SqlFragment", cs)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
self.fragments
.push(TokenTree::Ident(Ident::new("Binding", cs)));
let mut p1 = TokenTree::Punct(Punct::new('&', Spacing::Alone));
p1.set_span(span);
let mut p2 = TokenTree::Group(Group::new(Delimiter::Parenthesis, stream));
p2.set_span(span);
let mut t1 = TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([p1, p2]),
));
t1.set_span(span);
self.fragments.push(t1);
self.fragments
.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
}
fn flush_fragments(&mut self) {
if let Some(builder) = &self.builder {
self.flush_fragments_appender(builder.clone());
return;
}
let cs = Span::call_site();
if !self.text.is_empty() {
let s1 = self.fragments.len();
self.fragments
.push(TokenTree::from(Ident::new("simple_pg", cs)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
self.fragments
.push(TokenTree::Ident(Ident::new("SqlFragment", cs)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
self.fragments.push(TokenTree::Ident(Ident::new("Raw", cs)));
self.fragments.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from(TokenTree::Literal(Literal::string(&self.text))),
)));
if self.fragment_text_span.is_none() {
self.fragment_text_span = Some((s1, self.fragments.len()));
}
self.fragments
.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
self.text.clear();
}
if !self.captures.is_empty() {
self.fragments
.push(TokenTree::from(Ident::new("simple_pg", cs)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
self.fragments
.push(TokenTree::Ident(Ident::new("SqlFragment", cs)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
self.fragments
.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
self.fragments
.push(TokenTree::Ident(Ident::new("Bindings", cs)));
let captures = std::mem::take(&mut self.captures)
.into_iter()
.map(|(span, binding)| {
let mut t1 = TokenTree::Punct(Punct::new('&', Spacing::Alone));
t1.set_span(span);
let mut t2 = TokenTree::Group(Group::new(Delimiter::Parenthesis, binding));
t2.set_span(span);
let mut t3 = TokenTree::Punct(Punct::new(',', Spacing::Alone));
t3.set_span(span);
TokenStream::from_iter([t1, t2, t3])
});
self.fragments.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Group(Group::new(
Delimiter::Bracket,
TokenStream::from_iter(captures),
)),
]),
)));
self.fragments
.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
}
}
fn raw_fragment(&mut self, ident: Ident) {
self.flush_fragments();
let cs = ident.span();
self.fragments.push(TokenTree::Ident(ident));
self.fragments
.push(TokenTree::Punct(Punct::new('.', Spacing::Alone)));
self.fragments
.push(TokenTree::Ident(Ident::new("borrow_into_sql_fragment", cs)));
self.fragments.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::new(),
)));
self.fragments
.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
self.text.push(' ');
}
fn raw_fragment_group(&mut self, group: Group) {
self.flush_fragments();
self.fragments.push(with_span(
TokenTree::Group(Group::new(Delimiter::Parenthesis, group.stream())),
group.span(),
));
self.fragments.push(with_span(
TokenTree::Punct(Punct::new('.', Spacing::Alone)),
group.span(),
));
self.fragments.push(TokenTree::Ident(Ident::new(
"borrow_into_sql_fragment",
group.span(),
)));
self.fragments.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::new(),
)));
self.fragments
.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
self.text.push(' ');
}
fn assignment_macro(&mut self, input: TokenStream) {
let mut buf = std::mem::take(&mut self.text);
let mut toks = input.into_iter();
let mut first = true;
buf.push('(');
self.text.push_str(" VALUES (");
while let Some(next) = toks.next() {
if let TokenTree::Ident(ident) = &next {
if first {
first = false;
} else {
self.push_munch_ws(',');
self.text.push(' ');
buf.push(',');
buf.push(' ');
}
buf.push_str(&ident.to_string());
} else {
self.err(next.span(), format!("Expected column name after comma"));
return;
}
let Some(tok) = toks.next() else {
self.eof(next.span());
return;
};
if let TokenTree::Punct(punct) = &tok {
if punct.as_char() != ':' {
self.err(punct.span(), format!("Expected colon after column name"));
return;
}
} else {
self.err(tok.span(), format!("Expected colon after column name"));
return;
}
self.munch(&mut toks, Some(','));
}
self.push_munch_ws(')');
if self.fragments.is_empty() {
buf.push(')');
buf.push_str(&self.text);
self.text = buf;
}
}
fn add_binding(&mut self, span: Span, g: TokenStream) {
if self.fragments.is_empty() && self.builder.is_none() {
self.captures.push((span, g));
use std::fmt::Write;
write!(self.text, "${} ", self.captures.len()).ok();
} else {
self.flush_fragments();
self.add_binding_fragment(span, g);
}
}
fn err(&mut self, span: Span, message: String) {
if self.error.is_none() {
self.error = Some((span, message));
}
}
fn eof(&mut self, span: Span) {
if self.error.is_none() {
self.error = Some((span, "Unexpected EOF".into()))
}
}
fn push_munch_ws(&mut self, ch: char) {
unsafe {
let bytes = self.text.as_mut_vec();
while bytes.last() == Some(&b' ') {
bytes.pop();
}
}
self.text.push(ch)
}
fn munch(&mut self, input: &mut IntoIter, until: Option<char>) {
'outer: while let Some(mut t) = input.next() {
macro_rules! expect_next {
() => {
if let Some(got) = input.next() {
got
} else {
self.eof(Span::call_site());
return;
}
};
($token_kind: ident) => {
if let Some(got) = input.next() {
if let TokenTree::$token_kind(value) = got {
value
} else {
self.err(
got.span(),
concat!("Expected ", stringify!($token_kind)).to_string(),
);
continue 'outer;
}
} else {
self.eof(Span::call_site());
return;
}
};
}
'inner: loop {
match &t {
TokenTree::Group(g) => {
match g.delimiter() {
Delimiter::Parenthesis => self.text.push('('),
Delimiter::Brace => {
self.add_binding(g.span(), g.stream());
continue 'outer;
}
Delimiter::Bracket => self.push_munch_ws('['),
Delimiter::None => (),
}
if self.select_bindings_depth > 0 {
self.select_bindings_depth += 1;
}
self.reconstruct_from(g.stream().into_iter());
if self.select_bindings_depth > 0 {
self.select_bindings_depth -= 1;
}
match g.delimiter() {
Delimiter::Parenthesis => self.push_munch_ws(')'),
Delimiter::Brace => self.text.push('}'),
Delimiter::Bracket => self.push_munch_ws(']'),
Delimiter::None => (),
}
}
TokenTree::Punct(p) => {
let ch = p.as_char();
if let Some(until) = until {
if until == ch {
return;
}
}
if ch == '#' {
if let Some(tt) = input.next() {
if let TokenTree::Group(group) = tt {
self.raw_fragment_group(group);
continue 'outer;
} else if let TokenTree::Ident(ident) = tt {
let name = ident.to_string();
if name == "const" {
let Some(query) = input.next() else {
self.eof(ident.span());
return;
};
let TokenTree::Ident(query) = query else {
self.err(
query.span(),
format!("Expected ident after `const`"),
);
return;
};
let Some(cap) = input.next() else {
self.eof(query.span());
return;
};
let TokenTree::Ident(cap) = cap else {
self.err(
cap.span(),
format!("Expected `let` after const binding"),
);
return;
};
if cap.to_string() != "let" {
self.err(
cap.span(),
format!("Expected `let` after `const` binding"),
);
return;
}
let Some(bindings) = input.next() else {
self.eof(cap.span());
return;
};
let TokenTree::Ident(bindings) = bindings else {
self.err(
bindings.span(),
format!("Expected ident after `let`"),
);
return;
};
let Some(semi) = input.next() else {
self.eof(bindings.span());
return;
};
if semi.to_string() != ";" {
self.err(
semi.span(),
format!("Expected `;` after `let` binding"),
);
return;
}
self.binding = Some((query, bindings));
} else if name == "struct" {
let Some(struct_def) = input.next() else {
self.eof(ident.span());
return;
};
if let TokenTree::Group(group) = struct_def {
self.assignment_macro(group.stream());
continue 'outer;
} else {
self.err(struct_def.span(), format!("Expected brace after `struct`, found `{struct_def}`"));
continue 'outer;
}
} else if name == "ref" {
let binding = expect_next!(Ident);
let got = if let Some(got) = input.next() {
got
} else {
self.eof(Span::call_site());
return;
};
let Some(exa) = &self.ref_list else {
self.err(
ident.span(),
format!(
"#ref must be used inside a select column list"
),
);
return;
};
let (type_info, block) = match got {
TokenTree::Group(group) => (None, group),
TokenTree::Punct(ch) if ch.as_char() == ':' => {
if let Some((type_info, group)) =
take_until_group(input)
{
(Some(type_info), group)
} else {
self.err(
ch.span(),
format!("expected block for ref"),
);
return;
}
}
tt => {
self.err(
tt.span(),
format!(
"expected either colon then type ok block"
),
);
return;
}
};
let start = exa.columns.len().saturating_sub(1);
self.munch(&mut block.stream().into_iter(), None);
let Some(exa) = &mut self.ref_list else {
self.err(
ident.span(),
format!(
"#ref must be used inside a select column list"
),
);
return;
};
exa.bindsets.push(BindSet {
ty: type_info,
binding,
start,
end: exa.columns.len(),
});
continue 'outer;
} else {
self.raw_fragment(ident);
}
continue 'outer;
} else if let TokenTree::Literal(liter) = tt {
if let Some(ch) = self.text.as_bytes().last() {
if matches!(ch, b'>' | b'a'..=b'z' | b'A'..=b'Z' | b'.' | b'=')
{
self.text.push(' ');
}
}
let value = liter.to_string();
self.text.push_str(&value);
self.text.push(' ');
continue 'outer;
} else {
self.error = Some((
tt.span(),
format!("Unexpected, following '#' {tt}"),
));
continue 'outer;
}
} else {
todo!();
}
}
if ch == '$' {
let binding_span = t.span();
let Some(nt) = input.next() else {
self.eof(t.span());
return;
};
match nt {
TokenTree::Group(group) => {
if group.delimiter() == Delimiter::Parenthesis {
self.add_binding(group.span(), group.stream());
continue 'outer;
} else {
self.add_binding(
group.span(),
TokenStream::from_iter([TokenTree::Group(group)]),
);
continue 'outer;
}
}
ident @ TokenTree::Ident(_) => {
let mut binding = TokenStream::new();
let ident_span = ident.span();
binding.extend([ident]);
let mut expect_punct = true;
let mut only_ident = true;
while let Some(token) = input.next() {
match token {
group @ TokenTree::Group(_) => {
if expect_punct {
binding.extend([group]);
} else {
t = group;
self.add_binding(
if only_ident {
ident_span
} else {
binding_span
},
binding,
);
continue 'inner;
}
}
ident2 @ TokenTree::Ident(_) => {
if expect_punct {
t = ident2;
self.add_binding(
if only_ident {
ident_span
} else {
binding_span
},
binding,
);
continue 'inner;
} else {
binding.extend([ident2]);
expect_punct = true;
}
}
TokenTree::Punct(punct) => match punct.as_char() {
'.' => {
binding.extend([TokenTree::Punct(punct)]);
expect_punct = false;
}
'?' if expect_punct => {
binding.extend([TokenTree::Punct(punct)]);
expect_punct = true;
}
_ => {
t = TokenTree::Punct(punct);
self.add_binding(
if only_ident {
ident_span
} else {
binding_span
},
binding,
);
continue 'inner;
}
},
lit @ TokenTree::Literal(_) => {
if expect_punct {
t = lit;
self.add_binding(
if only_ident {
ident_span
} else {
binding_span
},
binding,
);
continue 'inner;
} else {
binding.extend([lit]);
expect_punct = false;
}
}
}
only_ident = false;
}
self.add_binding(
if only_ident { ident_span } else { binding_span },
binding,
);
return;
}
TokenTree::Punct(punct) => {
if punct.as_char() == '$' {
self.text.push('$');
continue 'outer;
}
if self.error.is_none() {
self.error = Some((
punct.span(),
format!("Unexpected `{punct}`, following '$'"),
));
}
continue 'outer;
}
literal => {
self.add_binding(
literal.span(),
TokenStream::from_iter([literal]),
);
continue 'outer;
}
}
}
if ch == ';' {
self.push_munch_ws(';');
self.stmt_kind = StmtKind::Unknown;
self.text.push('\n');
} else if ch == '.' {
self.push_munch_ws('.');
} else if ch == ',' {
if self.select_bindings_depth == 1 {
if let Some(refl) = &mut self.ref_list {
refl.columns.push(None);
}
}
self.push_munch_ws(',');
self.text.push(' ');
} else if ch == '*' {
self.text.push(ch);
self.text.push(' ');
} else {
self.text.push(ch);
}
}
TokenTree::Literal(l) => {
let value = l.to_string();
if let Some(ch) = self.text.as_bytes().last() {
if matches!(ch, b'>' | b'a'..=b'z' | b'A'..=b'Z' | b'.' | b'=') {
self.text.push(' ');
}
}
if value.starts_with('"') {
self.text
.push_str(&value.replace('\'', "\\'").replace('"', "'"));
} else {
self.text.push_str(&value);
}
self.text.push(' ');
}
TokenTree::Ident(l) => {
let value = l.to_string();
if self.select_bindings_depth == 1 {
if value.eq_ignore_ascii_case("as") {
let name = expect_next!(Ident);
self.text.push_str(&value);
self.text.push(' ');
self.text.push_str(&name.to_string());
self.text.push(' ');
if let Some(rl) = &mut self.ref_list {
*rl.columns.last_mut().unwrap() = Some(name);
}
continue 'outer;
} else if value.eq_ignore_ascii_case("from") {
self.select_bindings_depth = 0;
} else {
if let Some(rl) = &mut self.ref_list {
*rl.columns.last_mut().unwrap() = Some(l.clone());
}
}
}
if value.len() == 6 {
if value.eq_ignore_ascii_case("insert") {
self.stmt_kind = StmtKind::Insert;
} else if value.eq_ignore_ascii_case("update") {
self.stmt_kind = StmtKind::Update;
} else if self.ref_list.is_none()
&& value.eq_ignore_ascii_case("select")
{
self.ref_list = Some(ColumnRefList {
columns: vec![None],
bindsets: Vec::new(),
});
self.select_bindings_depth = 1;
}
}
self.text.push_str(&value);
self.text.push(' ');
}
}
break;
}
}
}
fn reconstruct_from(&mut self, mut input: IntoIter) {
self.munch(&mut input, None);
}
}
#[proc_macro]
pub fn append_sql(input: TokenStream) -> TokenStream {
let mut iter = input.into_iter();
let ident = iter.next().unwrap();
iter.next().unwrap();
let mut src = Source {
builder: Some(match ident {
TokenTree::Group(_) => todo!(),
TokenTree::Ident(ident) => ident,
TokenTree::Punct(_) => todo!(),
TokenTree::Literal(_) => todo!(),
}),
text: String::with_capacity(128),
captures: Vec::new(),
fragments: Vec::new(),
stmt_kind: StmtKind::Unknown,
binding: None,
error: None,
select_bindings_depth: 0,
ref_list: None,
fragment_text_span: None,
};
src.reconstruct_from(iter);
src.flush_fragments();
let mut stmts = TokenStream::from_iter(src.fragments);
if let Some((span, error)) = src.error {
let mut group = TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([TokenTree::Literal(Literal::string(&error))]),
));
let mut punc = TokenTree::Punct(Punct::new('!', Spacing::Alone));
punc.set_span(span);
group.set_span(span);
stmts.extend([
TokenTree::Ident(Ident::new("compile_error", span)),
punc,
group,
TokenTree::Punct(Punct::new(';', Spacing::Alone)),
])
}
stmts
}
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
let mut src = Source {
builder: None,
text: String::with_capacity(128),
captures: Vec::new(),
fragments: Vec::new(),
stmt_kind: StmtKind::Unknown,
binding: None,
error: None,
ref_list: None,
select_bindings_depth: 0,
fragment_text_span: None,
};
src.reconstruct_from(input.into_iter());
if src.fragments.is_empty() {
if let Some((query_string, bindings)) = src.binding {
let mut captures = TokenStream::default();
for (span, binding) in src.captures {
let mut t1 = TokenTree::Punct(Punct::new('&', Spacing::Alone));
t1.set_span(span);
let mut t2 = TokenTree::Group(Group::new(Delimiter::Parenthesis, binding));
t2.set_span(span);
let mut t3 = TokenTree::Punct(Punct::new(',', Spacing::Alone));
t3.set_span(span);
captures.extend([t1, t2, t3]);
}
let cs = Span::call_site();
let mut finally = TokenStream::from_iter([
TokenTree::from(Ident::new("const", cs)),
TokenTree::from(query_string),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::from(Ident::new("str", cs)),
TokenTree::Punct(Punct::new('=', Spacing::Alone)),
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Literal(Literal::string(&src.text)),
TokenTree::Punct(Punct::new(';', Spacing::Alone)),
TokenTree::from(Ident::new("let", cs)),
TokenTree::from(bindings),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Group(Group::new(
Delimiter::Bracket,
TokenStream::from_iter([
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([
TokenTree::from(Ident::new("dyn", cs)),
TokenTree::from(Ident::new("simple_pg", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::from(Ident::new("ToSql", cs)),
TokenTree::Punct(Punct::new('+', Spacing::Alone)),
TokenTree::from(Ident::new("Sync", cs)),
]),
)),
]),
)),
TokenTree::Punct(Punct::new('=', Spacing::Alone)),
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Group(Group::new(Delimiter::Bracket, captures)),
TokenTree::Punct(Punct::new(';', Spacing::Alone)),
]);
if let Some((span, error)) = src.error {
let mut group = TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([TokenTree::Literal(Literal::string(&error))]),
));
let mut punc = TokenTree::Punct(Punct::new('!', Spacing::Alone));
punc.set_span(span);
group.set_span(span);
finally.extend([
TokenTree::Ident(Ident::new("compile_error", span)),
punc,
group,
TokenTree::Punct(Punct::new(';', Spacing::Alone)),
])
}
finally
} else {
let mut captures = TokenStream::default();
for (span, binding) in src.captures {
let mut t1 = TokenTree::Punct(Punct::new('&', Spacing::Alone));
t1.set_span(span);
let mut t2 = TokenTree::Group(Group::new(Delimiter::Parenthesis, binding));
t2.set_span(span);
let mut t3 = TokenTree::Punct(Punct::new(',', Spacing::Alone));
t3.set_span(span);
captures.extend([t1, t2, t3]);
}
if let Some((span, error)) = src.error {
let mut group = TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([TokenTree::Literal(Literal::string(&error))]),
));
let mut punc = TokenTree::Punct(Punct::new('!', Spacing::Alone));
punc.set_span(span);
group.set_span(span);
captures.extend([
TokenTree::Ident(Ident::new("compile_error", span)),
punc,
group,
])
}
let mut text = TokenTree::Literal(Literal::string(src.text.trim()));
if let Some(ref_list) = src.ref_list {
text = ref_list.wrap(TokenStream::from_iter([text]));
}
let cs = Span::call_site();
TokenStream::from_iter([
TokenTree::from(Ident::new("simple_pg", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::from(Ident::new("Sql", cs)),
TokenTree::Group(Group::new(
Delimiter::Brace,
TokenStream::from_iter([
TokenTree::Ident(Ident::new("bindings", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Group(Group::new(Delimiter::Bracket, captures)),
TokenTree::Punct(Punct::new(',', Spacing::Alone)),
TokenTree::Ident(Ident::new("raw", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
text,
]),
)),
])
}
} else {
src.flush_fragments();
let cs = Span::call_site();
let mut fragments = if let Some(ref_list) = src.ref_list {
if let Some((start, end)) = src.fragment_text_span {
let mut ts = src.fragments.into_iter();
let mut first = TokenStream::from_iter(ts.by_ref().take(start));
first.extend(TokenStream::from_iter([
ref_list.wrap(TokenStream::from_iter(ts.by_ref().take(end - start)))
]));
first.extend(ts);
first
} else {
let mut ts = TokenStream::from_iter(src.fragments.into_iter());
ts.extend([ref_list.wrap(TokenStream::from_iter([
ident("simple_pg", cs),
chr(':'),
chrj(':'),
ident("Raw", cs),
TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([TokenTree::Literal(Literal::string(""))]),
)),
chr(','),
]))]);
ts
}
} else {
TokenStream::from_iter(src.fragments.into_iter())
};
if let Some((span, error)) = src.error {
let mut group = TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([TokenTree::Literal(Literal::string(&error))]),
));
let mut punc = TokenTree::Punct(Punct::new('!', Spacing::Alone));
punc.set_span(span);
group.set_span(span);
fragments.extend([
TokenTree::Ident(Ident::new("compile_error", span)),
punc,
group,
])
}
TokenStream::from_iter([
TokenTree::from(Ident::new("simple_pg", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::from(Ident::new("GeneratedSql", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::from(Ident::new("generated", cs)),
TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Group(Group::new(Delimiter::Bracket, fragments)),
]),
)),
])
}
}
#[proc_macro_attribute]
pub fn test(_args: TokenStream, item: TokenStream) -> TokenStream {
let mut tokens: Vec<TokenTree> = item.into_iter().collect();
for (i, tok) in tokens.iter().enumerate() {
if let TokenTree::Ident(ident) = tok {
if ident.to_string() == "async" {
tokens.remove(i);
break;
}
}
}
let cs = Span::call_site();
let body = tokens.pop().unwrap();
let new_body = TokenStream::from_iter([
TokenTree::Ident(Ident::new("let", cs)),
TokenTree::Ident(Ident::new("mut", cs)),
TokenTree::Ident(Ident::new("body", cs)),
TokenTree::Punct(Punct::new('=', Spacing::Alone)),
TokenTree::Ident(Ident::new("async", cs)),
body,
TokenTree::Punct(Punct::new(';', Spacing::Alone)),
TokenTree::Ident(Ident::new("simple_pg", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::Ident(Ident::new("block_on_test_runtime", cs)),
TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([
TokenTree::from(Ident::new("unsafe", cs)),
TokenTree::Group(Group::new(
Delimiter::Brace,
TokenStream::from_iter([
TokenTree::Ident(Ident::new("std", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::Ident(Ident::new("pin", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::Ident(Ident::new("Pin", cs)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Alone)),
TokenTree::Ident(Ident::new("new_unchecked", cs)),
TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter([
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::from(Ident::new("mut", cs)),
TokenTree::from(Ident::new("body", cs)),
]),
)),
]),
)),
]),
)),
]);
let mut ret = TokenStream::from_iter([
TokenTree::from(Punct::new('#', Spacing::Alone)),
TokenTree::Group(Group::new(
Delimiter::Bracket,
TokenStream::from_iter([TokenTree::from(Ident::new("test", cs))]),
)),
]);
tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
ret.extend(tokens);
return ret;
}