From ded42bef644c272e86e633220b18db835b786c5e Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 24 Feb 2020 03:06:21 -0600 Subject: [PATCH] before ptr --- .gitignore | 3 + .tokeignore | 1 + enterprise-compiler/src/lib.rs | 16 +++-- enterprise-compiler/src/model/mod.rs | 54 +++++++++++++-- enterprise-compiler/src/model/props.rs | 1 + enterprise-compiler/src/visitor.rs | 92 ++++++++++++++------------ enterprise-macros/src/tests.rs | 6 +- examples/todomvc/src/build.rs | 6 +- src/backend/web.rs | 6 +- src/lib.rs | 18 ++++- 10 files changed, 141 insertions(+), 62 deletions(-) diff --git a/.gitignore b/.gitignore index 53eaa21..db89c35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /target **/*.rs.bk + +graph.* +ouais.rs \ No newline at end of file diff --git a/.tokeignore b/.tokeignore index 27e6989..7acad07 100644 --- a/.tokeignore +++ b/.tokeignore @@ -1,2 +1,3 @@ syn-serde symbol +/ouais.rs diff --git a/enterprise-compiler/src/lib.rs b/enterprise-compiler/src/lib.rs index 0fae3cc..515f92e 100644 --- a/enterprise-compiler/src/lib.rs +++ b/enterprise-compiler/src/lib.rs @@ -24,7 +24,6 @@ pub fn build(component: &Component) -> TokenStream { let mut visitor = Visitor::new(); visitor.load_model(&component.model); let tagged_dom = visitor.make_graph(&component.view); - println!("New DOM: {:?}", tagged_dom); let toplevel_names = visitor.gen_code(&tagged_dom); // output the "model" @@ -60,15 +59,16 @@ pub fn build(component: &Component) -> TokenStream { for fn_name in toplevel_names.iter() { let fn_name = format_ident!("{}", fn_name); init_el_code.extend(quote! { - { - use enterprise::stdweb::web::INode; - let sub = self.#fn_name(); - el.append_child(&sub); - } + let sub = self.#fn_name(); + el.append_child(&sub); }); } quote! { + use enterprise::std::List; + use enterprise::stdweb::web::{INode, IElement, Node, IEventTarget}; + use crate::enterprise::stdweb::unstable::TryFrom; + pub struct #name { _b: std::marker::PhantomData, #model @@ -86,8 +86,10 @@ pub fn build(component: &Component) -> TokenStream { } impl enterprise::Component for #name { - fn create(&self, el: &enterprise::stdweb::web::Element) { + fn render(&self) -> Node { + let el = enterprise::stdweb::web::document().create_element("div").unwrap(); #init_el_code + el.as_node().clone() } } } diff --git a/enterprise-compiler/src/model/mod.rs b/enterprise-compiler/src/model/mod.rs index 9f0aff7..486455e 100644 --- a/enterprise-compiler/src/model/mod.rs +++ b/enterprise-compiler/src/model/mod.rs @@ -158,10 +158,24 @@ impl ToTokens for Elem { } } +pub type Context = BTreeMap; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum ContextVar { + Model(ModelValue), +} + +#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] +pub enum ModelValue { + Index(Box, Symbol), + Leaf(Symbol), +} + +/// "Rust + XML", taken from JSX: This represents a node in a DOM tree #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Rsx { Elem(Elem), - Code(Expr), + Code(Context, Expr), Text(String), ForLoop(Pat, Expr, Vec), @@ -173,7 +187,8 @@ impl PartialEq for Rsx { fn eq(&self, other: &Rsx) -> bool { match (self, other) { (Rsx::Elem(this), Rsx::Elem(other)) => this == other, - (Rsx::Code(expr), Rsx::Code(other)) => { + (Rsx::Code(ctx, expr), Rsx::Code(ctx2, other)) => { + ctx == ctx2 && syn::Expr::from_adapter(expr) == syn::Expr::from_adapter(other) } (Rsx::Text(this), Rsx::Text(other)) => this == other, @@ -190,7 +205,7 @@ impl ToTokens for Rsx { Rsx::Elem(elem) => { stream.extend(quote!(#elem)); } - Rsx::Code(expr) => { + Rsx::Code(_, expr) => { let expr = syn::Expr::from_adapter(expr); stream.extend(quote!({ #expr })); } @@ -212,10 +227,11 @@ impl ToTokens for Rsx { } } +/// The same as RSX, except at this point every node has been assigned an ID. #[derive(Debug)] pub enum TaggedRsx { Elem(Id, Elem), - Code(Id, Box), + Code(Id, Context, Box), Text(Id, String), ForLoop(Id, Pat, Expr, Vec), @@ -227,10 +243,38 @@ impl TaggedRsx { pub fn get_id(&self) -> Id { match self { TaggedRsx::Elem(id, _) - | TaggedRsx::Code(id, _) + | TaggedRsx::Code(id, _, _) | TaggedRsx::Text(id, _) | TaggedRsx::ForLoop(id, _, _, _) => *id, _ => unimplemented!("tagged rsx"), } } } + +impl ToTokens for TaggedRsx { + fn to_tokens(&self, stream: &mut TokenStream) { + match self { + TaggedRsx::Elem(_, elem) => { + stream.extend(quote!(#elem)); + } + TaggedRsx::Code(_, _, expr) => { + let expr = syn::Expr::from_adapter(expr); + stream.extend(quote!({ #expr })); + } + TaggedRsx::Text(_, string) => { + let string = syn::Lit::Str(syn::LitStr::new(string.as_ref(), Span::call_site())); + stream.extend(quote!(#string)); + } + TaggedRsx::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 ])); + } + TaggedRsx::_Nonexhaustive => unreachable!("should never be constructed"), + } + } +} diff --git a/enterprise-compiler/src/model/props.rs b/enterprise-compiler/src/model/props.rs index f81d3e1..f809c06 100644 --- a/enterprise-compiler/src/model/props.rs +++ b/enterprise-compiler/src/model/props.rs @@ -35,6 +35,7 @@ prop_compose! { pub fn arbitrary_view() -> impl Strategy { let leaf = prop_oneof![ Just(Rsx::Code( + BTreeMap::new(), syn::parse_str::("()").unwrap().to_adapter() )), string_regex(r"[:print:]+").unwrap().prop_map(Rsx::Text), diff --git a/enterprise-compiler/src/visitor.rs b/enterprise-compiler/src/visitor.rs index 331cd35..4583978 100644 --- a/enterprise-compiler/src/visitor.rs +++ b/enterprise-compiler/src/visitor.rs @@ -8,9 +8,9 @@ use quote::ToTokens; use syn::{Expr, Type}; use syn_serde::Syn; -use crate::model::{Elem, Id, ModelMap, Rsx, TagLhs, TagRhs, TaggedRsx}; +use crate::model::{Elem, Id, ModelValue, ModelMap, Rsx, ContextVar, TagLhs, TagRhs, TaggedRsx}; use crate::utils; -use crate::Symbol; +use crate::{Symbol}; /// A node within the dependency graph. #[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] @@ -40,12 +40,6 @@ pub enum DepAction { SubmitEvt, } -#[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] -pub enum ModelValue { - Index(Symbol, Symbol), - Leaf(Symbol), -} - impl DepNode { // Generates code for when this updates fn gen_update_code(&self, updates: &mut TokenStream, update_func: &mut TokenStream) { @@ -57,20 +51,14 @@ impl DepNode { let #inner_lock = self.#sym_name.clone(); }); update_func.extend(quote! { - { - let mut locked = #inner_lock.lock(); - *locked = new_value.clone(); - } + enterprise::ValueUpdatable::update(#inner_lock, new_value); }); } DepNode::RsxSpan(id) => { let id_str = id.as_str(); update_func.extend(quote! { - { - use enterprise::stdweb::web::{INonElementParentNode, INode}; - if let Some(target) = enterprise::stdweb::web::document().get_element_by_id(#id_str) { - target.set_text_content(&new_value.clone()); - } + if let Some(target) = enterprise::stdweb::web::document().get_element_by_id(#id_str) { + target.set_text_content(&new_value.clone()); } }); } @@ -85,7 +73,7 @@ impl DepNode { } } -type DependencyGraph = DiGraphMap; +type DependencyGraph = DiGraphMap, DepAction>; #[derive(Default, Debug)] pub struct Visitor { @@ -138,7 +126,6 @@ impl Visitor { .add_edge(attr_node, model_node, DepAction::ValueChange); self.deps .add_edge(model_node, attr_node, DepAction::ValueChange); - println!("Added elem attr to graph {:?} {:?}", attr_node, model_node); if let Some(set) = self.elem_attr_map.get_mut(&node_id) { set.insert(Symbol::from(attr)); } else { @@ -169,7 +156,12 @@ impl Visitor { let actual_expr = if let syn::Expr::Closure(syn::ExprClosure { inputs, body, .. }) = expr { let mut has_io = false; for arg in inputs.iter() { - if let syn::Pat::Ident(syn::PatIdent { ident, subpat: Some((_, subpat)), ..} ) = arg { + if let syn::Pat::Ident(syn::PatIdent { + ident, + subpat: Some((_, subpat)), + .. + }) = arg + { let bind = ident.to_string(); match (bind.as_ref(), &(**subpat)) { ("i", syn::Pat::Ident(syn::PatIdent { ident, .. })) => { @@ -203,7 +195,7 @@ impl Visitor { expr.clone() }; - self.code_segments.insert(code_node_id, expr.clone()); + self.code_segments.insert(code_node_id, actual_expr.clone()); // look for model references in the code segment // let names = self.get_model_names(); @@ -239,7 +231,7 @@ impl Visitor { } // Code changes are dependent on variables within the code segment in the model // Every time the model changes, the code segment must re-evaluate - Rsx::Code(expr) => { + Rsx::Code(ctx, expr) => { let syn_expr = Syn::from_adapter(&*expr); let names = self.get_model_names(); let deps = self.extract_model_dependencies_from_expr(&syn_expr, &names); @@ -249,7 +241,7 @@ impl Visitor { self.deps.add_edge(from, to, DepAction::ValueChange); } - TaggedRsx::Code(node_id, Box::new(syn_expr.clone().to_adapter())) + TaggedRsx::Code(node_id, ctx.clone(), Box::new(syn_expr.clone().to_adapter())) } Rsx::Text(literal) => TaggedRsx::Text(node_id, literal.clone()), // Process a for-loop @@ -284,7 +276,6 @@ impl Visitor { // - Using the List iterator, there's an update method that uses keys for child in inner { let deps = self.extract_rsx_dependents(&child, &names); - println!("children: {:?} {:?} => {:?}", names, child, deps); let from = DepNode::Iterator(node_id); for dep in deps { self.deps.add_edge(from, dep, DepAction::ValueChange); @@ -315,15 +306,11 @@ impl Visitor { let mut update_func = TokenStream::new(); while let Some(nx) = dfs.next(&self.deps) { if nx != starting { - nx.gen_update_code( - &mut updates, - &mut update_func, - ); + nx.gen_update_code(&mut updates, &mut update_func); } } updates.extend(quote! { { - use enterprise::stdweb::{web::IEventTarget, unstable::TryFrom}; 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(); @@ -333,14 +320,14 @@ impl Visitor { }); } } + let elem_as_str = format!("{}", node.to_token_stream()); self.impl_code.extend(quote! { - /// Rsx::Elem + #[doc = #elem_as_str] 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.as_node() + el.as_node().clone() } }); if let Some(inner) = inner { @@ -348,9 +335,34 @@ impl Visitor { } names.push(format!("{}", make_node_id)); } - TaggedRsx::Code(_, expr) => { + TaggedRsx::Code(_, ctx, expr) => { let expr = syn::Expr::from_adapter(expr); let code_id = format_ident!("code_{}", node_str); + + let mut init_code = TokenStream::new(); + for (id, ctxvar) in ctx.iter() { + let id = format_ident!("{}", id.to_string()); + init_code.extend(quote!(let #id =)); + match ctxvar { + ContextVar::Model(model_value) => { + // let line = Indexable::index(self.todos, key) + fn generate_model_value_code(val: &ModelValue) -> TokenStream { + match val { + ModelValue::Leaf(sym) => { + let name = format_ident!("{}", sym.to_string()); + quote!(self . #name) + } + ModelValue::Index(val, idx) => { + let idx = format_ident!("{}", idx.to_string()); + let val = generate_model_value_code(val); + quote!(Indexable :: index ( #val , #idx )) + } + } + } + } + } + } + self.impl_code.extend(quote! { /// Actual code fn #code_id(&self) -> impl std::string::ToString { @@ -359,10 +371,9 @@ impl Visitor { /// Code 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.as_node() + el.as_node().clone() } }); names.push(format!("{}", make_node_id)); @@ -387,20 +398,20 @@ impl Visitor { let name = format_ident!("{}", name); func_calls.extend(quote! { let sub = self.#name(); - el.append_child(sub); + el.append_child(&sub); }); } + self.impl_code.extend(quote! { /// Initialize for-loop fn #init_loop_id(&self) -> enterprise::stdweb::web::Node { let el = enterprise::stdweb::web::document().create_element("div").expect("shouldn't fail"); #func_calls - el.as_node() + el.as_node().clone() } /// Update for-loop fn #update_loop_id(&self) { - } }); names.push(format!("{}", init_loop_id)); @@ -417,7 +428,6 @@ impl Visitor { } fn extract_rsx_dependents(&self, rsx: &Rsx, names: &HashSet) -> HashSet { - println!(">>>>>>>>>>>>Extract rsx from {:?}", rsx); let mut result = HashSet::new(); match rsx { Rsx::Elem(elem) => { @@ -427,7 +437,7 @@ impl Visitor { } } } - Rsx::Code(code) => { + Rsx::Code(_, code) => { let code = syn::Expr::from_adapter(code); let code_deps = self .extract_model_dependencies_from_expr(&code, names) @@ -443,7 +453,6 @@ impl Visitor { } _ => (), } - println!("<<<<<<<<<<<<{:?}", result); result } @@ -453,7 +462,6 @@ impl Visitor { expr: &Expr, names: &HashSet, ) -> HashSet { - println!("Extracting {}", expr.to_token_stream()); let tokens = expr.to_token_stream(); let mut result = HashSet::new(); diff --git a/enterprise-macros/src/tests.rs b/enterprise-macros/src/tests.rs index ea0ecee..6013653 100644 --- a/enterprise-macros/src/tests.rs +++ b/enterprise-macros/src/tests.rs @@ -16,7 +16,7 @@ component! { } view { - +
    [for (key, line) in todos]
  • {line} "[x]"
  • @@ -26,6 +26,10 @@ component! { } } +// pub mod enterprise { +// include!("../../ouais.rs"); +// } + fn main() { let component: Component = serde_json::from_str(TodoMVC.as_ref()).unwrap(); diff --git a/examples/todomvc/src/build.rs b/examples/todomvc/src/build.rs index 6854df4..d1f5e90 100644 --- a/examples/todomvc/src/build.rs +++ b/examples/todomvc/src/build.rs @@ -9,10 +9,10 @@ component! { } view { - +
      - [for (key, todo) in todos] -
    • {todo} "[x]"
    • + [for (key, line) in todos] +
    • {line} "[x]"
    • [/for]
    } diff --git a/src/backend/web.rs b/src/backend/web.rs index a868b80..36dbe86 100644 --- a/src/backend/web.rs +++ b/src/backend/web.rs @@ -1,5 +1,5 @@ use crate::backend::Node; -use stdweb::web::{document, INonElementParentNode, Node as WebNode}; +use stdweb::web::{document, INode, INonElementParentNode, Node as WebNode}; use crate::backend::Backend; use crate::Component; @@ -13,8 +13,8 @@ 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.render(&el); - component.render(); + let sub = component.render(); + el.append_child(&sub); } } diff --git a/src/lib.rs b/src/lib.rs index 14fe9e0..f81ad87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ //! Enterprise is a backend-agnostic framework for developing server-client GUI applications. pub extern crate enterprise_compiler; +extern crate std as rust_std; // re-exports pub extern crate parking_lot; @@ -11,6 +12,11 @@ pub mod std; mod forloop; +use rust_std::sync::Arc; + +use parking_lot::Mutex; +use stdweb::web::Node; + pub use crate::backend::{Backend, Web}; /// Components are the building-blocks of enterprise applications. @@ -18,7 +24,7 @@ pub trait Component { /// TODO: replace this with a real init function. // fn create(&self, el: &crate::stdweb::web::Element); - fn render(&self) -> B::NodeType; + fn render(&self) -> Node; } /// Declares a mod @@ -30,3 +36,13 @@ macro_rules! enterprise_mod { } } } + +pub trait ValueUpdatable { + fn update(_: Arc>, _: String); +} + +impl ValueUpdatable for String { + fn update(value_ref: Arc>, new_value: String) { + *value_ref.lock() = new_value; + } +}