diff --git a/Cargo.lock b/Cargo.lock index 0cbd771..d63ec8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,7 +103,7 @@ dependencies = [ "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "symbol 0.1.0", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "syn-serde 0.2.0", ] @@ -117,7 +117,7 @@ dependencies = [ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", "symbol 0.1.0", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "syn-serde 0.2.0", "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -522,7 +522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -574,7 +574,7 @@ dependencies = [ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -589,7 +589,7 @@ dependencies = [ "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -619,7 +619,7 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -636,7 +636,7 @@ dependencies = [ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -667,7 +667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -722,7 +722,7 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -742,7 +742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -842,7 +842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum stdweb-internal-macros 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" "checksum stdweb-internal-runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +"checksum syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a0294dc449adc58bb6592fff1a23d3e5e6e235afc6a0ffca2657d19e7bbffe5" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db" "checksum thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4" diff --git a/enterprise-compiler/Cargo.toml b/enterprise-compiler/Cargo.toml index 25f8041..b92972a 100644 --- a/enterprise-compiler/Cargo.toml +++ b/enterprise-compiler/Cargo.toml @@ -4,12 +4,14 @@ version = "0.1.0" authors = ["Michael Zhang "] edition = "2018" +[dev-dependencies] +proptest = "0.9.5" +proptest-derive = "0.1.2" + [dependencies] bimap = "0.4.0" petgraph = "0.5.0" proc-macro2 = "1.0.8" -proptest = "0.9.5" -proptest-derive = "0.1.2" quote = "1.0.2" serde = "1.0.104" serde_derive = "1.0.104" diff --git a/enterprise-compiler/src/lib.rs b/enterprise-compiler/src/lib.rs index 7167090..633471c 100644 --- a/enterprise-compiler/src/lib.rs +++ b/enterprise-compiler/src/lib.rs @@ -12,28 +12,36 @@ use std::fs::File; use std::io::Write; use std::path::PathBuf; +pub use crate::visitor::Visitor; + use crate::model::Component; -use crate::visitor::Visitor; use proc_macro2::TokenStream; use symbol::Symbol; -pub fn build( - // name: impl AsRef, - // datamodel: &HashMap, - // datainit: &HashMap, - // dom: &[Rsx], - component: &Component, -) -> TokenStream { +pub fn build(component: &Component) -> TokenStream { let name = &component.name; let mut visitor = Visitor::new(); visitor.load_model(&component.model); - let new_dom = visitor.make_graph(&component.view); - let toplevel_names = visitor.gen_code(&new_dom); - - // let graph: Graph<_, _, _> = visitor.deps.clone().into_graph(); - // println!("{:?}", Dot::new(&graph)); + let tagged_dom = visitor.make_graph(&component.view); + println!("New DOM: {:?}", tagged_dom); + let toplevel_names = visitor.gen_code(&tagged_dom); + // output the "model" + // looks a little bit like + // struct Name { + // _b: PhantomData, + // name: Type, + // } + // + // impl Name { + // pub fn new() -> Self { + // Self { + // _b: PhantomData::new(), + // name: value, + // } + // } + // } let name = format_ident!("{}", name); let mut model = TokenStream::new(); let mut init = TokenStream::new(); @@ -66,7 +74,7 @@ pub fn build( #model } - impl #name { + impl #name { pub fn new(_: &B) -> Self { #name { _b: std::marker::PhantomData::default(), @@ -90,5 +98,6 @@ pub fn process(mod_name: impl AsRef, code: impl AsRef) { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let mut out_file = File::create(out_dir.join(format!("{}.rs", mod_name.as_ref()))).unwrap(); let tokens = build(&component); + write!(out_file, "use enterprise::widgets::*;\n").unwrap(); write!(out_file, "{}", tokens).unwrap(); } diff --git a/enterprise-compiler/src/model/mod.rs b/enterprise-compiler/src/model/mod.rs index 223a8ee..44db50d 100644 --- a/enterprise-compiler/src/model/mod.rs +++ b/enterprise-compiler/src/model/mod.rs @@ -1,4 +1,9 @@ +//! The enterprise DSL model. + +#[cfg(test)] mod props; +#[cfg(test)] +pub use self::props::*; use std::collections::BTreeMap; use std::iter::{self, FromIterator}; @@ -6,9 +11,7 @@ use std::iter::{self, FromIterator}; use proc_macro2::{Span, TokenStream, TokenTree}; use quote::ToTokens; use symbol::Symbol; -use syn_serde::{Expr, Syn, Type}; - -pub use self::props::*; +use syn_serde::{Expr, Pat, Syn, Type}; pub type Id = Symbol; @@ -160,7 +163,7 @@ pub enum Rsx { Elem(Elem), Code(Expr), Text(String), - ForLoop(), + ForLoop(Pat, Expr, Vec), #[doc(hidden)] _Nonexhaustive, @@ -195,8 +198,14 @@ impl ToTokens for Rsx { let string = syn::Lit::Str(syn::LitStr::new(string.as_ref(), Span::call_site())); stream.extend(quote!(#string)); } - Rsx::ForLoop() => { - stream.extend(quote!({ for } { / for })); + Rsx::ForLoop(pat, expr, inner) => { + let expr = syn::Expr::from_adapter(expr); + let pat = syn::Pat::from_adapter(pat); + let mut inner_stream = TokenStream::new(); + for rsx in inner { + inner_stream.extend(rsx.to_token_stream()); + } + stream.extend(quote!([ for #pat in #expr ] #inner_stream [ / for ])); } Rsx::_Nonexhaustive => unreachable!("should never be constructed"), } @@ -208,6 +217,7 @@ pub enum TaggedRsx { Elem(Id, Elem), Code(Id, Box), Text(Id, String), + ForLoop(Id, Pat, Expr, Vec), #[doc(hidden)] _Nonexhaustive, @@ -216,7 +226,10 @@ pub enum TaggedRsx { impl TaggedRsx { pub fn get_id(&self) -> Id { match self { - TaggedRsx::Elem(id, _) | TaggedRsx::Code(id, _) | TaggedRsx::Text(id, _) => *id, + TaggedRsx::Elem(id, _) + | TaggedRsx::Code(id, _) + | TaggedRsx::Text(id, _) + | TaggedRsx::ForLoop(id, _, _, _) => *id, _ => unimplemented!("tagged rsx"), } } diff --git a/enterprise-compiler/src/model/props.rs b/enterprise-compiler/src/model/props.rs index 906e604..f81d3e1 100644 --- a/enterprise-compiler/src/model/props.rs +++ b/enterprise-compiler/src/model/props.rs @@ -1,3 +1,5 @@ +//! Utility functions for property checking. + use std::collections::BTreeMap; use proptest::{ diff --git a/enterprise-compiler/src/tuple_map.rs b/enterprise-compiler/src/tuple_map.rs index fbb6b7e..6a158ba 100644 --- a/enterprise-compiler/src/tuple_map.rs +++ b/enterprise-compiler/src/tuple_map.rs @@ -1,4 +1,6 @@ -// https://github.com/daboross/serde-tuple-vec-map/blob/master/src/lib.rs +//! Utility library for converting a map whose keys are not strings to JSON, which requires string keys. +//! +//! https://github.com/daboross/serde-tuple-vec-map/blob/master/src/lib.rs use std::fmt; diff --git a/enterprise-compiler/src/visitor.rs b/enterprise-compiler/src/visitor.rs index de667d6..b9bc790 100644 --- a/enterprise-compiler/src/visitor.rs +++ b/enterprise-compiler/src/visitor.rs @@ -1,8 +1,9 @@ +//! Visitor that traverses a model and generates code. + use std::collections::HashMap; use std::collections::HashSet; -use petgraph::graphmap::DiGraphMap; -use petgraph::visit::Dfs; +use petgraph::{dot::Dot, graphmap::DiGraphMap, visit::Dfs}; use proc_macro2::{TokenStream, TokenTree}; use quote::ToTokens; use syn::{Expr, Type}; @@ -11,25 +12,25 @@ use syn_serde::Syn; use crate::model::{Elem, Id, ModelMap, Rsx, TagLhs, TagRhs, TaggedRsx}; use crate::Symbol; +/// A node within the dependency graph. #[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] pub enum DepNode { - // This is an attribute on an element - // Not read-only + /// This is an attribute on an element + /// + /// Not read-only RsxAttr(Symbol, Symbol), - // This is a text node (innertext) - // These are read-only + /// This is a text node (innertext) + /// + /// These are read-only RsxSpan(Symbol), - // This is something in the model + /// This is something in the model ModelValue(Symbol), + /// This is an iterator (for-loop) + Iterator(Symbol), } impl DepNode { - fn gen_update_code( - &self, - // model_bimap: &BiHashMap, - updates: &mut TokenStream, - update_func: &mut TokenStream, - ) { + fn gen_update_code(&self, updates: &mut TokenStream, update_func: &mut TokenStream) { match self { DepNode::ModelValue(sym) => { let sym_name = format_ident!("{}", sym.to_string()); @@ -80,6 +81,10 @@ impl Visitor { } } + pub fn impl_code(&self) -> &TokenStream { + &self.impl_code + } + pub fn load_model(&mut self, model: &ModelMap) { for (key, (ty, init)) in model { let ty = Syn::from_adapter(&*ty); @@ -89,28 +94,34 @@ impl Visitor { // self.model.extend(model.clone()); } + pub fn dot(&self) -> Dot<&DependencyGraph> { + Dot::new(&self.deps) + } + pub fn make_graph(&mut self, nodes: &[Rsx]) -> Vec { let mut new_nodes = Vec::new(); for node in nodes { let node_id = Symbol::gensym(); let new_node = match node { + // Process a < /> tag Rsx::Elem(Elem { tag, attrs, inner }) => { let tag_inner = inner.as_ref().map(|inner| self.make_graph(inner)); for (lhs, rhs) in attrs { - if let TagLhs::Bind(attr) = lhs { - if let TagRhs::Text(text) = rhs { - let text_sym = Symbol::from(text); - if self.model.contains_key(&text_sym) { - let from = DepNode::RsxAttr(node_id, Symbol::from(attr)); - let to = DepNode::ModelValue(text_sym); - self.deps.add_edge(from, to, ()); - if let Some(set) = self.elem_attr_map.get_mut(&node_id) { - set.insert(Symbol::from(attr)); - } else { - let mut set = HashSet::new(); - set.insert(Symbol::from(attr)); - self.elem_attr_map.insert(node_id, set); - } + // If the left-hand side contains bind:attr="name", put that attribute as a dependency of name + if let (TagLhs::Bind(attr), TagRhs::Text(text)) = (lhs, rhs) { + let text_sym = Symbol::from(text); + // check if the model actually contains the key that you're trying to bind to + if self.model.contains_key(&text_sym) { + let attr_node = DepNode::RsxAttr(node_id, Symbol::from(attr)); + let model_node = DepNode::ModelValue(text_sym); + self.deps.add_edge(attr_node, model_node, ()); + self.deps.add_edge(model_node, attr_node, ()); + if let Some(set) = self.elem_attr_map.get_mut(&node_id) { + set.insert(Symbol::from(attr)); + } else { + let mut set = HashSet::new(); + set.insert(Symbol::from(attr)); + self.elem_attr_map.insert(node_id, set); } } } @@ -136,7 +147,24 @@ impl Visitor { TaggedRsx::Code(node_id, Box::new(syn_expr.clone().to_adapter())) } Rsx::Text(literal) => TaggedRsx::Text(node_id, literal.clone()), - _ => unimplemented!(), + // Process a for-loop + // The for-loop should exist by itself in the dep tree + // Everything inside the for loop is a dependency + Rsx::ForLoop(pat, expr, inner) => { + let new_inner = self.make_graph(inner.as_ref()); + + // figure out which variables in the iterator that it depends on + // for example, [for x in y] depends on y. + let syn_expr = Syn::from_adapter(&*expr); + let deps = self.extract_model_dependencies(&syn_expr); + for dep in deps { + let from = DepNode::ModelValue(dep); + let to = DepNode::Iterator(node_id); + self.deps.add_edge(from, to, ()); + } + TaggedRsx::ForLoop(node_id, pat.clone(), expr.clone(), new_inner) + } + unknown => unimplemented!("unknown rsx: {:?}", unknown), }; new_nodes.push(new_node); } @@ -178,40 +206,66 @@ impl Visitor { } } self.impl_code.extend(quote! { - fn #make_node_id(&self) -> impl enterprise::stdweb::web::INode { + /// Rsx::Elem + fn #make_node_id(&self) -> enterprise::stdweb::web::Node { use enterprise::stdweb::web::IElement; let el = enterprise::stdweb::web::document().create_element(#tag).unwrap(); el.set_attribute("id", #node_str).unwrap(); #updates - el + el.as_node() } }); if let Some(inner) = inner { self.gen_code(inner); } + names.push(format!("{}", make_node_id)); } - TaggedRsx::Code(_, _) => { + TaggedRsx::Code(_, expr) => { + let expr = syn::Expr::from_adapter(expr); + let code_id = format_ident!("code_{}", node_str); self.impl_code.extend(quote! { + fn #code_id(&self) -> impl std::string::ToString { + #expr + } + #[inline] - fn #make_node_id(&self) -> impl enterprise::stdweb::web::INode { + fn #make_node_id(&self) -> enterprise::stdweb::web::Node { use enterprise::stdweb::web::IElement; let el = enterprise::stdweb::web::document().create_element("span").expect("shouldn't fail"); el.set_attribute("id", #node_str).unwrap(); - el + el.as_node() } }); + names.push(format!("{}", make_node_id)); } TaggedRsx::Text(_, literal) => { self.impl_code.extend(quote! { #[inline] - fn #make_node_id(&self) -> impl enterprise::stdweb::web::INode { - enterprise::stdweb::web::document().create_text_node(#literal) + fn #make_node_id(&self) -> enterprise::stdweb::web::Node { + let text = enterprise::widgets::Text::new(#literal); + text.render() + // enterprise::stdweb::web::document().create_text_node(#literal) } }); + names.push(format!("{}", make_node_id)); + } + TaggedRsx::ForLoop(_, _, _, inner) => { + let init_loop_id = format_ident!("init_loop_{}", node_str); + let mut func_calls = TokenStream::new(); + for name in self.gen_code(&inner) { + let name = format_ident!("{}", name); + func_calls.extend(quote! { self.#name(); }); + } + self.impl_code.extend(quote! { + #[inline] + fn #init_loop_id(&self) -> enterprise::stdweb::web::Node { + #func_calls + } + }); + names.push(format!("{}", init_loop_id)); } _ => unimplemented!("gen_code tagged rsx"), } - names.push(format!("{}", make_node_id)); } names } diff --git a/enterprise-macros/Cargo.toml b/enterprise-macros/Cargo.toml index 9d36b75..c6f248f 100644 --- a/enterprise-macros/Cargo.toml +++ b/enterprise-macros/Cargo.toml @@ -7,6 +7,10 @@ edition = "2018" [lib] proc-macro = true +[[bin]] +name = "test" +path = "src/tests.rs" + [dev-dependencies] proptest = "0.9.5" diff --git a/enterprise-macros/src/parser.rs b/enterprise-macros/src/parser.rs index 6c05cb5..11fb877 100644 --- a/enterprise-macros/src/parser.rs +++ b/enterprise-macros/src/parser.rs @@ -9,7 +9,7 @@ use proc_macro2::{ use symbol::Symbol; use syn::{ parse::{Parse, ParseStream}, - Error as SynError, Expr, Lit, Result as SynResult, Token, Type, + Error as SynError, Expr, Lit, Pat, Result as SynResult, Token, Type, }; use syn_serde::Syn; @@ -152,7 +152,6 @@ impl Visitor { } buf.push(next_token); } - println!("model buf: {:?}", buf); // probably shouldn't happen? if buf.len() == 0 { @@ -173,7 +172,6 @@ impl Visitor { let mut map = BTreeMap::new(); while let Some((name, ty, init, comma)) = single_def()? { - println!("single_def => ({}, {:?}, {:?}, {})", name, ty, init, comma); map.insert(name, (ty, init)); if !comma { break; @@ -185,7 +183,7 @@ impl Visitor { fn consume_view(&mut self) -> Result, ParseError> { enum Container { Tag(String, BTreeMap), - ForLoop(), + ForLoop(Pat, Expr), } let mut rsx_parser = RsxParser::new(self.0.clone()); @@ -234,12 +232,13 @@ impl Visitor { RsxToken::Str(string) => { result.push(Rsx::Text(string)); } - RsxToken::OpeningFor() => { - tag_stack.push((Container::ForLoop(), result.clone())); + RsxToken::OpeningFor(pat, expr) => { + tag_stack.push((Container::ForLoop(pat, expr), result.clone())); } RsxToken::ClosingFor => { - if let Some((Container::ForLoop(), mut last_result)) = tag_stack.pop() { - last_result.push(Rsx::ForLoop()); + if let Some((Container::ForLoop(pat, expr), mut last_result)) = tag_stack.pop() + { + last_result.push(Rsx::ForLoop(pat.to_adapter(), expr.to_adapter(), result)); result = last_result; } else { return Err(ParseError::ClosedTooFar); diff --git a/enterprise-macros/src/rsx.rs b/enterprise-macros/src/rsx.rs index 7745311..480bacf 100644 --- a/enterprise-macros/src/rsx.rs +++ b/enterprise-macros/src/rsx.rs @@ -161,7 +161,7 @@ impl RsxParser { kw_for: Token![for], pat: Pat, kw_in: Token![in], - iter: Expr, + expr: Expr, } impl Parse for ForLoopHeader { @@ -170,7 +170,7 @@ impl RsxParser { kw_for: input.parse()?, pat: input.parse()?, kw_in: input.parse()?, - iter: input.parse()?, + expr: input.parse()?, }) } } @@ -179,10 +179,11 @@ impl RsxParser { match stream.peek() { Some(TokenTree::Ident(ident)) if &ident.to_string() == "for" => { let for_loop = syn::parse2::(group.stream())?; - Ok(Some(RsxToken::OpeningFor())) + Ok(Some(RsxToken::OpeningFor(for_loop.pat, for_loop.expr))) } Some(TokenTree::Punct(punct)) if punct.as_char() == '/' => { stream.next(); + // TODO: check that it's actuall closing the for-loop Ok(Some(RsxToken::ClosingFor)) } Some(token) => Err(ParseError::UnexpectedToken(token.clone())), @@ -215,7 +216,7 @@ pub(crate) enum RsxToken { ClosingTag(Symbol), Str(String), Code(Expr), - OpeningFor(), + OpeningFor(Pat, Expr), ClosingFor, } diff --git a/enterprise-macros/src/tests.rs b/enterprise-macros/src/tests.rs new file mode 100644 index 0000000..63a1288 --- /dev/null +++ b/enterprise-macros/src/tests.rs @@ -0,0 +1,43 @@ +#[macro_use] +extern crate enterprise_macros; + +use std::fs::File; +use std::io::Write; + +use enterprise_compiler::model::Component; +use enterprise_compiler::Visitor; + +component! { + component TodoMVC { + model { + value: String = "", + todos: List = List::new(), + } + + view { + +
    + [for (key, todo) in todos] +
  • {todo} "[x]"
  • + [/for] +
+ } + } +} + +fn main() { + let component: Component = serde_json::from_str(TodoMVC.as_ref()).unwrap(); + + let mut visitor = Visitor::new(); + visitor.load_model(&component.model); + let tagged_dom = visitor.make_graph(&component.view); + // println!("Tagged dom: {:?}", tagged_dom); + + let toplevel_names = visitor.gen_code(&tagged_dom); + // println!("Toplevel names: {:?}", toplevel_names); + println!("Impl code: {}", &visitor.impl_code()); + + let all_code = enterprise_compiler::build(&component); + let mut file = File::create("ouais.rs").unwrap(); + write!(file, "{}", all_code).unwrap(); +} diff --git a/examples/helloworld/src/build.rs b/examples/helloworld/src/build.rs index 9248d35..bd7f0ff 100644 --- a/examples/helloworld/src/build.rs +++ b/examples/helloworld/src/build.rs @@ -8,6 +8,7 @@ component! { } view { + "Hello, " {name} "!" } diff --git a/examples/helloworld/src/main.rs b/examples/helloworld/src/main.rs index fc36228..ae6e07e 100644 --- a/examples/helloworld/src/main.rs +++ b/examples/helloworld/src/main.rs @@ -1,18 +1,141 @@ -#[macro_use] -extern crate enterprise; +// #[macro_use] +// extern crate enterprise; -enterprise_mod!(helloworld); +// enterprise_mod!(helloworld); -use enterprise::{Backend, Web}; +// use enterprise::{Backend, Web}; -use crate::helloworld::HelloWorld; +// use crate::helloworld::HelloWorld; -fn main() { - stdweb::initialize(); +// fn main() { +// stdweb::initialize(); - let web = Web; - let app = HelloWorld::new(&web); - web.initialize(app, "app".into()); +// let web = Web; +// let app = HelloWorld::new(&web); +// web.initialize(app, "app".into()); - stdweb::event_loop(); +// stdweb::event_loop(); +// } + +use stdweb::web::INode; + +pub struct HelloWorld { + _b: std::marker::PhantomData, + name: std::sync::Arc>, +} +impl HelloWorld { + pub fn new(_: &B) -> Self { + HelloWorld { + _b: std::marker::PhantomData::default(), + name: std::sync::Arc::new(enterprise::parking_lot::Mutex::new("hello".into())), + } + } + fn make_sym_0(&self) -> B::NodeType { + use enterprise::stdweb::web::IElement; + let el = enterprise::stdweb::web::document() + .create_element("input") + .unwrap(); + el.set_attribute("id", "sym_0").unwrap(); + let inner_lock_sym_5 = self.name.clone(); + { + use enterprise::stdweb::{unstable::TryFrom, web::IEventTarget}; + let inner_el = el.clone(); + el.add_event_listener(move |evt: enterprise::stdweb::web::event::InputEvent| { + let new_value = + enterprise::stdweb::web::html_element::InputElement::try_from(inner_el.clone()) + .unwrap() + .raw_value(); + { + let mut locked = inner_lock_sym_5.lock(); + *locked = new_value.clone(); + } + { + use enterprise::stdweb::web::{INode, INonElementParentNode}; + if let Some(target) = + enterprise::stdweb::web::document().get_element_by_id("sym_3") + { + target.set_text_content(&new_value.clone()); + } + } + }); + } + el.as_node().clone() + } + fn make_sym_1(&self) -> B::NodeType { + use enterprise::stdweb::web::IElement; + let el = enterprise::stdweb::web::document() + .create_element("input") + .unwrap(); + el.set_attribute("id", "sym_1").unwrap(); + let inner_lock_sym_6 = self.name.clone(); + { + use enterprise::stdweb::{unstable::TryFrom, web::IEventTarget}; + let inner_el = el.clone(); + el.add_event_listener(move |evt: enterprise::stdweb::web::event::InputEvent| { + let new_value = + enterprise::stdweb::web::html_element::InputElement::try_from(inner_el.clone()) + .unwrap() + .raw_value(); + { + let mut locked = inner_lock_sym_6.lock(); + *locked = new_value.clone(); + } + { + use enterprise::stdweb::web::{INode, INonElementParentNode}; + if let Some(target) = + enterprise::stdweb::web::document().get_element_by_id("sym_3") + { + target.set_text_content(&new_value.clone()); + } + } + }); + } + el.as_node().clone() + } + #[inline] + fn make_sym_2(&self) -> B::NodeType { + enterprise::stdweb::web::document().create_text_node("Hello, ") + } + #[inline] + fn make_sym_3(&self) -> B::NodeType { + use enterprise::stdweb::web::IElement; + let el = enterprise::stdweb::web::document() + .create_element("span") + .expect("shouldn't fail"); + el.set_attribute("id", "sym_3").unwrap(); + el.as_node().clone() + } + #[inline] + fn make_sym_4(&self) -> B::NodeType { + enterprise::stdweb::web::document().create_text_node("!") + } +} +impl enterprise::Component for HelloWorld { + fn create(&self, el: &enterprise::stdweb::web::Element) { + { + use enterprise::stdweb::web::INode; + let sub = self.make_sym_0(); + el.append_child(&sub); + } + { + use enterprise::stdweb::web::INode; + let sub = self.make_sym_1(); + el.append_child(&sub); + } + { + use enterprise::stdweb::web::INode; + let sub = self.make_sym_2(); + el.append_child(&sub); + } + { + use enterprise::stdweb::web::INode; + let sub = self.make_sym_3(); + el.append_child(&sub); + } + { + use enterprise::stdweb::web::INode; + let sub = self.make_sym_4(); + el.append_child(&sub); + } + } } diff --git a/examples/todomvc/src/build.rs b/examples/todomvc/src/build.rs index 657d41e..6854df4 100644 --- a/examples/todomvc/src/build.rs +++ b/examples/todomvc/src/build.rs @@ -4,11 +4,12 @@ extern crate enterprise_macros; component! { component TodoMVC { model { + value: String = "", todos: List = List::new(), } view { - +
    [for (key, todo) in todos]
  • {todo} "[x]"
  • diff --git a/output.txt b/output.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 4cd0393..b049849 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -13,4 +13,8 @@ pub trait Backend: Sized { /// Initializes the backend with the given component. fn initialize>(&self, _: C, _: Self::InitParams); + + type NodeType: Node; } + +pub trait Node {} diff --git a/src/backend/web.rs b/src/backend/web.rs index 968a7c5..a868b80 100644 --- a/src/backend/web.rs +++ b/src/backend/web.rs @@ -1,4 +1,5 @@ -use stdweb::web::{document, INonElementParentNode}; +use crate::backend::Node; +use stdweb::web::{document, INonElementParentNode, Node as WebNode}; use crate::backend::Backend; use crate::Component; @@ -12,7 +13,12 @@ impl Backend for Web { fn initialize>(&self, component: C, params: Self::InitParams) { let id = params.as_ref(); if let Some(el) = document().get_element_by_id(id) { - component.create(&el); + // component.render(&el); + component.render(); } } + + type NodeType = WebNode; } + +impl Node for WebNode {} diff --git a/src/lib.rs b/src/lib.rs index 3c9c9db..14fe9e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ //! Enterprise is a backend-agnostic framework for developing server-client GUI applications. -#![deny(missing_docs)] - pub extern crate enterprise_compiler; // re-exports @@ -18,7 +16,9 @@ pub use crate::backend::{Backend, Web}; /// Components are the building-blocks of enterprise applications. pub trait Component { /// TODO: replace this with a real init function. - fn create(&self, el: &crate::stdweb::web::Element); + // fn create(&self, el: &crate::stdweb::web::Element); + + fn render(&self) -> B::NodeType; } /// Declares a mod diff --git a/src/std/list.rs b/src/std/list.rs index a9a8b74..fb1e1c1 100644 --- a/src/std/list.rs +++ b/src/std/list.rs @@ -10,7 +10,6 @@ use symbol::Symbol; /// - O(1) insertion /// - O(1) deletion /// - O(1) lookup -/// - O(n) iteration pub struct List { head: Option>>, tail: Option>>, @@ -110,6 +109,15 @@ impl List { IntoIter(self.head) } + /// Gets the item using its key + pub fn get(&self, key: &Symbol) -> Option<&T> { + if let Some(node) = self.map.get(key) { + unsafe { node.as_ref() }.data.as_ref() + } else { + None + } + } + /// Returns the head of the list pub fn head(&self) -> Option<&T> { if let Some(ref node) = self.head { diff --git a/src/std/mod.rs b/src/std/mod.rs index 8af9a51..09a101f 100644 --- a/src/std/mod.rs +++ b/src/std/mod.rs @@ -1,5 +1,7 @@ //! Standard modules mod list; +mod widgets; pub use self::list::List; +pub use self::widgets::*; diff --git a/src/std/widgets/input_box.rs b/src/std/widgets/input_box.rs new file mode 100644 index 0000000..feea5b2 --- /dev/null +++ b/src/std/widgets/input_box.rs @@ -0,0 +1,28 @@ +use std::any::TypeId; +use std::borrow::Borrow; +use std::collections::HashMap; + +use crate::backend::{Backend, Web}; +use crate::Component; + +thread_local! { + static INPUT_BOX: HashMap Box>> = HashMap::new(); +} + +pub trait InputBox {} + +pub fn InputBox(name: impl AsRef) -> Option> { + let name = name.as_ref(); + let ty = TypeId::of::(); + INPUT_BOX.with(move |input_box| { + if let Some(func) = input_box.get(&ty) { + Some(func(name.to_string())) + } else { + None + } + }) +} + +pub struct InputBoxWeb {} + +impl InputBox for InputBoxWeb {} diff --git a/src/std/widgets/mod.rs b/src/std/widgets/mod.rs new file mode 100644 index 0000000..a8327c4 --- /dev/null +++ b/src/std/widgets/mod.rs @@ -0,0 +1,5 @@ +mod input_box; +mod text; + +use self::input_box::*; +use self::text::*; diff --git a/src/std/widgets/text.rs b/src/std/widgets/text.rs new file mode 100644 index 0000000..fb3a4aa --- /dev/null +++ b/src/std/widgets/text.rs @@ -0,0 +1,17 @@ +use crate::backend::Web; +use crate::Component; +use stdweb::web::{document, INode, Node}; + +pub struct Text(String); + +impl Text { + pub fn new(string: impl AsRef) -> Self { + Self(string.as_ref().to_string()) + } +} + +impl Component for Text { + fn render(&self) -> Node { + document().create_text_node(&self.0).as_node().clone() + } +}