use proc_macro2::{Ident, TokenStream};
use proc_macro_error::{abort, ResultExt};
use quote::{quote, quote_spanned};
use syn::{
parse::{discouraged::Speculative, ParseStream, Parser},
parse_quote,
spanned::Spanned,
Expr, ExprPath, Token,
};
use syn_rsx::{Node, NodeAttribute, NodeName};
pub fn view(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let (context, rest) = parse_context(input.into());
let nodes = syn_rsx::parse2(rest).unwrap_or_abort();
let nodes = nodes.iter().map(|node| view_node(&context, node));
let expanded = if nodes.len() == 1 {
quote! {
#(#nodes)*
}
} else {
quote! {
ori::core::Div::new()
#( .child(#nodes) )*
}
};
expanded.into()
}
fn parse_context(input: TokenStream) -> (Expr, TokenStream) {
let parser = |parser: ParseStream| {
if parser.is_empty() {
return Ok((parse_quote! { cx }, TokenStream::new()));
}
let fork = parser.fork();
let Ok(expr) = fork.parse::<Expr>() else {
return Ok((parse_quote! { cx }, parser.parse()?));
};
if fork.peek(Token![,]) {
fork.parse::<Token![,]>()?;
} else {
return Ok((parse_quote! { cx }, parser.parse()?));
}
parser.advance_to(&fork);
Ok((expr, parser.parse()?))
};
Parser::parse2(parser, input).unwrap_or_abort()
}
fn view_node(context: &Expr, node: &Node) -> TokenStream {
match node {
Node::Element(element) => {
let name = &element.name;
let mut attributes = Vec::new();
let mut properties = Vec::new();
for node in &element.attributes {
let attr = get_attribute(&node);
attribute(context, name, attr, &mut attributes, &mut properties);
}
let children = element.children.iter().map(|node| {
let child = view_node(context, node);
quote! {
<#name as ori::core::Parent>::add_child(&mut view, #child);
}
});
quote! {
ori::core::BoundedScope::dynamic(#context, move |#context| {
let mut view = <#name as ori::core::Styleable<_>>::styled(
<#name as ::std::default::Default>::default()
);
#(#properties)*
#(#attributes)*
#(#children)*
view
})
}
}
Node::Block(block) => {
let expr = block.value.as_ref();
quote_spanned!(expr.span() =>
#[allow(unused_braces)]
#expr
)
}
Node::Comment(comment) => {
let comment = comment.value.as_ref();
quote! {
ori::core::Comment::new(#comment)
}
}
_ => unreachable!(),
}
}
fn get_attribute(node: &Node) -> &NodeAttribute {
let Node::Attribute(attribute) = node else {
unreachable!()
};
attribute
}
fn attribute(
context: &Expr,
name: &NodeName,
attr: &NodeAttribute,
attributes: &mut Vec<TokenStream>,
properties: &mut Vec<TokenStream>,
) {
if let NodeName::Path(ref path) = attr.key {
if let Some(ref value) = attr.value {
properties.push(property(name, &path, &value));
} else {
properties.push(quote!(true));
}
return;
}
if let NodeName::Punctuated(ref punct) = attr.key {
let (kind, key) = attribute_kind(attr);
match kind.as_str() {
"on" => {
if let Some(ref value) = attr.value {
let key = Ident::new(&key, punct.span());
properties.push(event(context, name, &key, &value));
} else {
abort!(punct, "expected event handler");
}
}
"bind" => {
if let Some(ref value) = attr.value {
let key = Ident::new(&key, punct.span());
properties.push(binding(context, name, &key, &value));
} else {
abort!(punct, "expected binding");
}
}
"style" => {
if let Some(ref value) = attr.value {
attributes.push(style(context, name, &key, &value));
} else {
abort!(punct, "expected attribute");
}
}
_ => abort!(kind, "invalid attribute kind"),
}
return;
}
}
fn attribute_kind(attribute: &NodeAttribute) -> (String, String) {
let NodeName::Punctuated(ref punct) = attribute.key else {
unreachable!()
};
let mut pairs = punct.pairs();
let pair = pairs.next().unwrap();
let kind = pair.value().clone();
if pair.punct().unwrap().as_char() != ':' {
abort!(punct, "expected ':'");
}
let mut key = String::new();
for pair in pairs {
let ident = pair.value().clone();
key.push_str(&ident.to_string());
if let Some(punct) = pair.punct() {
if punct.as_char() != '-' {
abort!(punct, "expected '-'");
}
key.push('-');
}
}
if key.is_empty() {
abort!(punct, "expected attribute name");
}
(kind.to_string(), key)
}
fn property(name: &NodeName, key: &ExprPath, value: &Expr) -> TokenStream {
if key.path == parse_quote!(class) {
return quote_spanned! {value.span() =>
view = <ori::core::Styled<#name> as ori::core::Styleable<_>>::class(view, #value);
};
}
let key = quote_spanned! {key.path.span() =>
#key
};
quote_spanned! {value.span() =>
<#name as ori::core::Properties>::setter(&mut view).#key(#value);
}
}
fn event(context: &Expr, name: &NodeName, key: &Ident, value: &Expr) -> TokenStream {
quote! {
<#name as ori::core::Events>::setter(&mut view).#key(#context, #value);
}
}
fn binding(context: &Expr, name: &NodeName, key: &Ident, value: &Expr) -> TokenStream {
quote! {
<#name as ori::core::Bindings>::setter(&mut view).#key(#context, #value);
}
}
fn style(_context: &Expr, name: &NodeName, key: &str, value: &Expr) -> TokenStream {
quote! {
view = <ori::core::Styled<#name> as ori::core::Styleable<_>>::attr(view, #key, #value);
}
}