1use std::convert::TryFrom;
3
4use quote::quote;
5use syn::Error;
6
7mod tokens;
8use tokens::{AttributeToken, ViewToken};
9
10fn partition_unzip<S, T, F>(items: impl Iterator<Item = S>, f: F) -> (Vec<T>, Vec<Error>)
11where
12 F: Fn(S) -> Result<T, Error>,
13{
14 let (tokens, errs): (Vec<Result<_, _>>, _) = items.map(f).partition(Result::is_ok);
15 let tokens = tokens
16 .into_iter()
17 .filter_map(Result::ok)
18 .collect::<Vec<_>>();
19 let errs = errs.into_iter().filter_map(Result::err).collect::<Vec<_>>();
20 (tokens, errs)
21}
22
23fn combine_errors(errs: Vec<Error>) -> Option<Error> {
24 errs.into_iter()
25 .fold(None, |may_prev_error: Option<Error>, err| {
26 if let Some(mut prev_error) = may_prev_error {
27 prev_error.combine(err);
28 Some(prev_error)
29 } else {
30 Some(err)
31 }
32 })
33}
34
35fn node_to_builder_token_stream(view_token: &ViewToken) -> Result<proc_macro2::TokenStream, Error> {
36 let view_path = quote! { mogwai::builder::ViewBuilder };
37 match view_token {
38 ViewToken::Element {
39 name,
40 name_span: _,
41 attributes,
42 children,
43 } => {
44 let may_type = attributes.iter().find_map(|att| match att {
45 AttributeToken::CastType(expr) => {
46 Some(quote! { as mogwai::builder::ViewBuilder<#expr> })
47 }
48 _ => None,
49 });
50
51 let type_is = may_type
52 .unwrap_or_else(|| quote! {as mogwai::builder::ViewBuilder<mogwai::view::Dom>});
53
54 let mut errs = vec![];
55 let (attribute_tokens, attribute_errs) =
56 partition_unzip(attributes.iter(), AttributeToken::try_builder_token_stream);
57 errs.extend(attribute_errs);
58
59 let (child_tokens, child_errs) =
60 partition_unzip(children.iter(), node_to_builder_token_stream);
61 let child_tokens = child_tokens.into_iter().map(|child| {
62 quote! {
63 .append(#child)
64 }
65 });
66 errs.extend(child_errs);
67
68 let may_error = combine_errors(errs);
69 if let Some(error) = may_error {
70 Err(error)
71 } else {
72 let create = quote! {#view_path::element(#name)};
73 Ok(quote! {
74 #create
75 #(#attribute_tokens)*
76 #(#child_tokens)*
77 #type_is
78 })
79 }
80 }
81 ViewToken::Text(expr) => Ok(quote! {mogwai::builder::ViewBuilder::text(#expr)}),
82 ViewToken::Block(expr) => Ok(quote! {
83 #[allow(unused_braces)]
84 #expr
85 }),
86 }
87}
88
89#[proc_macro]
90pub fn builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
102 let tokens = match syn_rsx::parse(input) {
103 Ok(parsed) => {
104 let (view_tokens, errs) = partition_unzip(parsed.into_iter(), ViewToken::try_from);
105 if let Some(error) = combine_errors(errs) {
106 return error.to_compile_error().into();
107 }
108 let (tokens, errs) = partition_unzip(view_tokens.iter(), node_to_builder_token_stream);
109 if let Some(error) = combine_errors(errs) {
110 return error.to_compile_error().into();
111 }
112
113 match tokens.len() {
114 0 => quote! { compile_error("dom/hydrate macro must not be empty") },
115 1 => {
116 let ts = &tokens[0];
117 quote! { #ts }
118 }
119 _ => quote! { vec![#(#tokens),*] },
120 }
121 }
122 Err(error) => error.to_compile_error(),
123 };
124
125 proc_macro::TokenStream::from(tokens)
126}
127
128#[proc_macro]
129pub fn view(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
142 let builder: proc_macro2::TokenStream = builder(input).into();
143 let token = quote! {{
144 use std::convert::TryFrom;
145 mogwai::view::View::try_from(#builder).unwrap()
146 }};
147 proc_macro::TokenStream::from(token)
148}
149
150#[proc_macro]
151pub fn target_arch_is_wasm32(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
152 proc_macro::TokenStream::from(quote! {
153 cfg!(target_arch = "wasm32")
154 })
155}
156
157#[cfg(test)]
158mod ssr_tests {
159 use std::str::FromStr;
160
161 #[test]
162 fn can_parse_rust_closure() {
163 let expr: syn::Expr = syn::parse_str(r#"|i:i32| format!("{}", i)"#).unwrap();
164 match expr {
165 syn::Expr::Closure(_) => {}
166 _ => panic!("wrong expr parse, expected closure"),
167 }
168 }
169
170 #[test]
171 fn can_token_stream_from_string() {
172 let _ts = proc_macro2::TokenStream::from_str(r#"|i:i32| format!("{}", i)"#).unwrap();
173 }
174
175 #[test]
176 fn can_parse_from_token_stream() {
177 let _ts = proc_macro2::TokenStream::from_str(r#"<div class="any_class" />"#).unwrap();
178 }
179}