diff --git a/enterprise-compiler/src/lib.rs b/enterprise-compiler/src/lib.rs index 42a1aef..65726c4 100644 --- a/enterprise-compiler/src/lib.rs +++ b/enterprise-compiler/src/lib.rs @@ -11,7 +11,9 @@ use proc_macro2::{Span, TokenStream, TokenTree}; use quote::ToTokens; use syn::{punctuated::Punctuated, Expr, ExprPath, Ident, Path, PathArguments, PathSegment}; -#[derive(Debug, PartialEq, Eq, Hash)] +type Id = String; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] enum TagLhs { Bind(String), On(String), @@ -19,18 +21,32 @@ enum TagLhs { } #[derive(Debug, Default)] -struct Tag { +struct Elem { tag: String, attrs: HashMap, - inner: Vec, + inner: Vec, } #[derive(Debug)] enum Rsx { - Tag(Tag), + Elem(Elem), Code(Expr), Text(String), - // For(String, String, Vec), +} + +#[derive(Debug)] +enum TaggedRsx { + Elem(Id, Elem), + Code(Id, Expr), + Text(Id, String), +} + +impl TaggedRsx { + pub fn get_id(&self) -> &str { + match self { + TaggedRsx::Elem(id, _) | TaggedRsx::Code(id, _) | TaggedRsx::Text(id, _) => id.as_ref(), + } + } } #[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] @@ -45,7 +61,7 @@ enum DepActions { Updates, } -#[derive(Debug)] +#[derive(Default, Debug)] struct DependencyGraph(DiGraphMap); impl DependencyGraph { @@ -55,21 +71,19 @@ impl DependencyGraph { } } -#[derive(Debug)] +#[derive(Default, Debug)] struct Visitor { idx: u32, deps: DependencyGraph, wtf: HashMap, model: HashMap, + impl_code: TokenStream, } impl Visitor { fn new() -> Visitor { Visitor { - idx: 0, - deps: DependencyGraph::new(), - wtf: HashMap::new(), - model: HashMap::new(), + ..Default::default() } } @@ -92,27 +106,81 @@ impl Visitor { next } - fn visit(&mut self, nodes: &[Rsx]) { - for node in nodes { - let node_id = self.unique_name("node"); - println!("Visiting {}: {:?}", node_id, node); - match node { - Rsx::Tag(Tag { tag, attrs, inner }) => { - self.visit(inner); - } - Rsx::Code(expr) => { - let deps = extract_model_dependencies(expr); - for dep in deps { - if self.model.contains_key(&dep) { - let from = self.unique_idx(DepNode::ModelValue); - let to = self.unique_idx(DepNode::RsxSpan); - self.deps.0.add_edge(from, to, ()); - } + fn make_graph(&mut self, nodes: &[Rsx]) -> Vec { + nodes + .iter() + .map(|node| { + let node_id = self.unique_name("node"); + match node { + Rsx::Elem(Elem { tag, attrs, inner }) => { + let tag_inner = self.make_graph(&inner); + TaggedRsx::Elem( + node_id, + Elem { + tag: tag.to_string(), + attrs: attrs.clone(), + inner: tag_inner, + }, + ) } + Rsx::Code(expr) => { + let deps = extract_model_dependencies(expr); + for dep in deps { + if self.model.contains_key(&dep) { + let from = self.unique_idx(DepNode::ModelValue); + let to = self.unique_idx(DepNode::RsxSpan); + self.deps.0.add_edge(from, to, ()); + } + } + + TaggedRsx::Code(node_id, expr.clone()) + } + Rsx::Text(literal) => TaggedRsx::Text(node_id, literal.clone()), + } + }) + .collect() + } + + fn gen_code(&mut self, nodes: &[TaggedRsx]) -> Vec { + let mut names = Vec::new(); + for node in nodes { + let node_id = node.get_id(); + let make_node_id = format_ident!("make_{}", node_id); + match node { + TaggedRsx::Elem(node_id, Elem { tag, attrs, inner }) => { + self.impl_code.extend(quote! { + fn #make_node_id(&self) -> impl stdweb::web::IElement { + let el = document().create_element(#tag).unwrap(); + el + } + }); + self.gen_code(&inner); + } + TaggedRsx::Code(node_id, expr) => { + self.impl_code.extend(quote! { + #[inline] + fn #make_node_id(&self) -> impl stdweb::web::IElement { + let el = document().create_element("span").expect("shouldn't fail"); + el.set_attribute("id", #node_id); + el + } + }); + } + TaggedRsx::Text(node_id, literal) => { + self.impl_code.extend(quote! { + #[inline] + fn #make_node_id(&self) -> impl stdweb::web::IElement { + let el = document().create_element("span").expect("shouldn't fail"); + el.set_text_content(#literal); + el + } + }); } _ => (), } + names.push(format!("{}", make_node_id)); } + names } } @@ -132,23 +200,23 @@ fn extract_model_dependencies(expr: &Expr) -> HashSet { #[proc_macro] pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { - let todomvc_name = "TodoMVC".to_string(); + let helloworld_name = "HelloWorld".to_string(); - let todomvc_datamodel: HashMap = hashmap! { + let helloworld_datamodel: HashMap = hashmap! { "name".into() => "String".into(), }; - let todomvc_datainit: HashMap = hashmap! { + let helloworld_datainit: HashMap = hashmap! { "name".into() => "\"world\".into()".into(), }; - let todomvc_dom = vec![ - Rsx::Tag(Tag { + let helloworld_dom = vec![ + Rsx::Elem(Elem { tag: "input".into(), attrs: hashmap! { TagLhs::Bind("value".into()) => "name".into(), }, - ..Tag::default() + inner: vec![], }), Rsx::Text("Hello, ".into()), Rsx::Code(syn::parse_str::("name").unwrap()), @@ -156,15 +224,16 @@ pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream ]; let mut visitor = Visitor::new(); - visitor.load_model(&todomvc_datamodel); - visitor.visit(&todomvc_dom); + visitor.load_model(&helloworld_datamodel); + let new_dom = visitor.make_graph(&helloworld_dom); + let toplevel_names = visitor.gen_code(&new_dom); - println!("{:?}", visitor); + // println!("{:?}", visitor); println!("DOT:"); let graph: Graph<_, _, _> = visitor.deps.0.clone().into_graph(); println!("{:?}", Dot::new(&graph)); - let name = format_ident!("{}", todomvc_name); + let name = format_ident!("{}", helloworld_name); let mut model = TokenStream::new(); let mut init = TokenStream::new(); for (name, ty) in visitor.model { @@ -173,26 +242,42 @@ pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream let ty = format_ident!("{}", ty); model.extend(quote! { #name : #ty , }); } - for (name, value) in todomvc_datainit { + for (name, value) in helloworld_datainit { let name = format_ident!("{}", name); let value = syn::parse_str::(&value).unwrap(); init.extend(quote! { #name : #value , }); } + + let impl_code = &visitor.impl_code; + let mut init_el_code = TokenStream::new(); + for fn_name in toplevel_names.iter() { + let fn_name = format_ident!("{}", fn_name); + init_el_code.extend(quote! { + let sub = self.#fn_name(); + el.append_child(&sub); + }); + } + let result = quote! { - struct #name { + struct #name { + _b: std::marker::PhantomData, #model } - impl #name { - fn new() -> Self { + impl #name { + fn new(_: &B) -> Self { #name { + _b: std::marker::PhantomData::default(), #init } } + + #impl_code } - impl crate::Component for #name { + impl crate::Component for #name { fn initialize(&self, el: &crate::Element) { + #init_el_code } } }; diff --git a/src/backend.rs b/src/backend.rs new file mode 100644 index 0000000..e250422 --- /dev/null +++ b/src/backend.rs @@ -0,0 +1,5 @@ +pub trait Backend {} + +pub struct Web; + +impl Backend for Web {} diff --git a/src/main.rs b/src/main.rs index 0f142ba..d58a32c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,26 +3,32 @@ extern crate enterprise_compiler; #[macro_use] extern crate stdweb; -use stdweb::web::{document, Element, INonElementParentNode}; +mod backend; -trait Component { +use stdweb::web::{document, Element, INode, IElement, INonElementParentNode}; + +use crate::backend::{Backend, Web}; + +trait Component { fn initialize(&self, element: &Element); } example!(); -fn render(component: &C, id: impl AsRef) { +fn render>(component: &C, id: impl AsRef) { let id = id.as_ref(); if let Some(el) = document().get_element_by_id(id) { - component.initialize(&el); - } + println!("Rendering..."); + component.initialize(&el); + } } fn main() { stdweb::initialize(); - let todomvc = TodoMVC::new(); - render(&todomvc, ""); + let web = Web; + let app = HelloWorld::new(&web); + render(&app, "app"); let message = "Hello world!"; js! { console.log(@{message}); } diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..99ec130 --- /dev/null +++ b/static/index.html @@ -0,0 +1,11 @@ + + + + + what the Hek + + +
+ + + \ No newline at end of file