extern crate proc_macro;
use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
#[proc_macro]
pub fn html(src: TokenStream) -> TokenStream {
html1(src, false)
}
fn html1(src: TokenStream, is_inner: bool) -> TokenStream {
let mut tokens = Vec::new();
if !is_inner {
tokens.push(TokenTree::Ident(Ident::new("let", Span::call_site())));
tokens.push(TokenTree::Ident(Ident::new("mut", Span::call_site())));
tokens.push(TokenTree::Ident(Ident::new("__html", Span::call_site())));
tokens.push(TokenTree::Punct(Punct::new('=', Spacing::Alone)));
tokens.push(TokenTree::Ident(Ident::new("String", Span::call_site())));
tokens.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
tokens.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
tokens.push(TokenTree::Ident(Ident::new("new", Span::call_site())));
tokens.push(TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::new(),
)));
tokens.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
}
let mut string = String::new();
parse(src, &mut string, &mut tokens);
if !string.is_empty() {
tokens.push(TokenTree::Ident(Ident::new("__html", Span::call_site())));
tokens.push(TokenTree::Punct(Punct::new('.', Spacing::Alone)));
tokens.push(TokenTree::Ident(Ident::new("push_str", Span::call_site())));
tokens.push(TokenTree::Group(Group::new(Delimiter::Parenthesis, {
[TokenTree::Literal(Literal::string(&string))]
.into_iter()
.collect()
})));
tokens.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
}
if !is_inner {
tokens.push(TokenTree::Ident(Ident::new("__html", Span::call_site())));
}
[TokenTree::Group(Group::new(
Delimiter::Brace,
tokens.into_iter().collect(),
))]
.into_iter()
.collect()
}
fn parse(src: TokenStream, string: &mut String, tokens: &mut Vec<TokenTree>) {
let mut src = src.into_iter().peekable();
while let Some(token) = src.next() {
match token {
TokenTree::Ident(_) if matches!(src.peek(), Some(TokenTree::Ident(_))) => {
string.push_str(&token.to_string());
string.push(' ');
}
TokenTree::Group(group) => match group.delimiter() {
Delimiter::Brace => {
parse_rust_block(group.stream(), string, tokens);
}
Delimiter::None => {
parse(group.stream(), string, tokens);
}
Delimiter::Bracket => {
string.push('[');
parse(group.stream(), string, tokens);
string.push(']');
}
Delimiter::Parenthesis => {
parse_rust_expr(group.stream(), string, tokens);
}
},
_ => {
string.push_str(&token.to_string());
}
}
}
}
fn parse_rust_block(src: TokenStream, string: &mut String, tokens: &mut Vec<TokenTree>) {
if !string.is_empty() {
tokens.push(TokenTree::Ident(Ident::new("__html", Span::call_site())));
tokens.push(TokenTree::Punct(Punct::new('.', Spacing::Alone)));
tokens.push(TokenTree::Ident(Ident::new("push_str", Span::call_site())));
tokens.push(TokenTree::Group(Group::new(Delimiter::Parenthesis, {
[TokenTree::Literal(Literal::string(string))]
.into_iter()
.collect()
})));
tokens.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
string.clear();
}
let mut src = src.into_iter().peekable();
while let Some(token) = src.next() {
match &token {
TokenTree::Group(g) => {
let mut content = Vec::new();
parse_rust_block(g.stream(), string, &mut content);
tokens.push(TokenTree::Group(Group::new(
g.delimiter(),
content.into_iter().collect(),
)));
}
TokenTree::Ident(ident)
if ident.to_string() == "html"
&& matches!(src.peek(), Some(TokenTree::Punct(_))) =>
{
let next = src.next().unwrap();
if next.to_string() == "!" {
if matches!(src.peek(), Some(TokenTree::Group(_))) {
let TokenTree::Group(group) = src.next().unwrap() else {
panic!();
};
let content = html1(group.stream(), true);
tokens.extend(content.into_iter());
} else {
tokens.push(next);
}
} else {
tokens.push(next);
}
}
_ => tokens.push(token),
}
}
}
fn parse_rust_expr(src: TokenStream, string: &mut String, tokens: &mut Vec<TokenTree>) {
if !string.is_empty() {
tokens.push(TokenTree::Ident(Ident::new("__html", Span::call_site())));
tokens.push(TokenTree::Punct(Punct::new('.', Spacing::Alone)));
tokens.push(TokenTree::Ident(Ident::new("push_str", Span::call_site())));
tokens.push(TokenTree::Group(Group::new(Delimiter::Parenthesis, {
[TokenTree::Literal(Literal::string(string))]
.into_iter()
.collect()
})));
tokens.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
string.clear();
}
tokens.push(TokenTree::Ident(Ident::new("__html", Span::call_site())));
tokens.push(TokenTree::Punct(Punct::new('.', Spacing::Alone)));
tokens.push(TokenTree::Ident(Ident::new("push_str", Span::call_site())));
tokens.push(TokenTree::Group(Group::new(Delimiter::Parenthesis, {
[
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Ident(Ident::new("format", Span::call_site())),
TokenTree::Punct(Punct::new('!', Spacing::Alone)),
TokenTree::Group(Group::new(Delimiter::Parenthesis, {
[
TokenTree::Literal(Literal::string("{}")),
TokenTree::Punct(Punct::new(',', Spacing::Alone)),
]
.into_iter()
.chain(src)
.collect()
})),
]
.into_iter()
.collect()
})));
tokens.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
}