osui-macros 0.1.0

Macros for osui-rs
Documentation
//! # RSX Emission
//!
//! Converts parsed RSX AST into Rust code that constructs RSX objects.

use crate::parse::*;
use proc_macro2::TokenStream;
use quote::quote;

/// Emits code for the root RSX
pub fn emit_rsx(root: RsxRoot) -> TokenStream {
    emit_rsx_vec(&root.nodes)
}

/// Emits code for a vector of RSX nodes
pub fn emit_rsx_vec(nodes: &Vec<RsxNode>) -> TokenStream {
    let nodes = nodes.iter().map(emit_node_scope);

    quote! {{
        let mut r = osui::frontend::Rsx::new();
        #(#nodes)*
        r
    }}
}

/// Emits variable bindings for dependencies
fn emit_deps(deps: &[Dep]) -> TokenStream {
    deps.iter()
        .map(|d| {
            let ident = &d.ident;
            if let Some(pat) = &d.pat {
                quote!( let #pat = #ident.clone(); )
            } else {
                quote!( let #ident = #ident.clone(); )
            }
        })
        .collect()
}

/// Emits a Vec of dependencies as HookDependency trait objects
fn emit_deps_vec(deps: &[Dep]) -> TokenStream {
    let deps = deps.iter().map(|d| {
        let ident = &d.ident;

        quote! {
            std::sync::Arc::new(#ident) as std::sync::Arc<dyn HookDependency>
        }
    });

    quote! {
        vec![ #(#deps),* ]
    }
}

/// Emits code for a single node within a scope
fn emit_node_scope(node: &RsxNode) -> TokenStream {
    match node {
        RsxNode::Text(_) => {
            let emit = emit_node(node);
            quote! {
                r.static_scope(move |scope| {#emit});
            }
        }

        RsxNode::Component { .. } => {
            let emit = emit_node(node);
            quote! {
                r.static_scope(move |scope| {#emit});
            }
        }

        RsxNode::Mount(m) => quote! {
            #m.mount();
        },

        RsxNode::If {
            deps,
            cond,
            children,
        } => {
            let deps_emit = emit_deps(deps);
            let deps_vec_emit = emit_deps_vec(deps);
            let kids = children.iter().map(emit_node);

            quote! {
                {
                    #deps_emit
                    r.dynamic_scope(move |scope| {
                        if #cond {
                            if scope.children.lock().unwrap().is_empty() {
                                #(#kids)*
                            }
                        } else {
                            scope.children.lock().unwrap().clear();
                        }
                    }, #deps_vec_emit);
                }
            }
        }

        RsxNode::For {
            deps,
            pat,
            expr,
            children,
        } => {
            let deps_emit = emit_deps(deps);
            let deps_vec_emit = emit_deps_vec(deps);
            let kids = children.iter().map(emit_node);

            quote! {
                {
                    #deps_emit
                    #[allow(unused_parens)]
                    r.dynamic_scope(move |scope| {
                        scope.children.lock().unwrap().clear();
                        for #pat in #expr {
                            #(#kids)*
                        }
                    }, #deps_vec_emit);
                }
            }
        }

        RsxNode::Expr(expr) => quote! {
            r.child(#expr);
        },
    }
}

fn emit_node(node: &RsxNode) -> TokenStream {
    match node {
        RsxNode::Text(text) => quote! {
            scope.view(Arc::new(move |ctx| {
                ctx.draw_text(Point { x: 0, y: 0 }, &format!(#text))
            }));
        },

        RsxNode::Component {
            path,
            props,
            children,
        } => {
            let prop_inits = props.iter().map(|p| {
                let name = &p.name;
                let value = &p.value;
                quote! { #name: #value }
            });

            let emit_children = emit_rsx_vec(children);

            let component_expr = if children.len() > 0 {
                quote! {
                    #path {
                        #(#prop_inits,)*
                        children: #emit_children
                    }
                }
            } else {
                quote! {
                    #path {
                        #(#prop_inits,)*
                    }
                }
            };

            quote! {
                scope.child(#component_expr, None);
            }
        }

        RsxNode::Mount(m) => quote! {
            #m.mount();
        },

        RsxNode::If { .. } => panic!("Invalid if statement"),

        RsxNode::For { .. } => panic!("Invalid for loop"),

        RsxNode::Expr(expr) => quote! {
            r.child(#expr);
        },
    }
}