diff --git a/Cargo.lock b/Cargo.lock index d63ec8a..49c3424 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,7 @@ dependencies = [ name = "enterprise-macros" version = "0.1.0" dependencies = [ + "enterprise 0.1.0", "enterprise-compiler 0.1.0", "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/enterprise-compiler/src/lib.rs b/enterprise-compiler/src/lib.rs index 515f92e..9540b2a 100644 --- a/enterprise-compiler/src/lib.rs +++ b/enterprise-compiler/src/lib.rs @@ -3,6 +3,7 @@ extern crate quote; #[macro_use] extern crate serde_derive; +mod graph; pub mod model; mod utils; mod visitor; @@ -12,11 +13,13 @@ use std::fs::File; use std::io::Write; use std::path::PathBuf; -pub use crate::visitor::Visitor; +use petgraph::dot::Dot; +use proc_macro2::TokenStream; +use quote::ToTokens; +use symbol::Symbol; use crate::model::Component; -use proc_macro2::TokenStream; -use symbol::Symbol; +pub use crate::visitor::Visitor; pub fn build(component: &Component) -> TokenStream { let name = &component.name; @@ -26,6 +29,11 @@ pub fn build(component: &Component) -> TokenStream { let tagged_dom = visitor.make_graph(&component.view); let toplevel_names = visitor.gen_code(&tagged_dom); + println!("Code segments:"); + for (l, r) in visitor.code_segments() { + println!("{:?}: {}", l, r); + } + // output the "model" // looks a little bit like // struct Name { @@ -48,9 +56,9 @@ pub fn build(component: &Component) -> TokenStream { let name = format_ident!("{}", name.as_str()); let ty: syn::Type = ty.into(); let value: syn::Expr = value.into(); - model.extend(quote! { #name : std::sync::Arc> , }); + model.extend(quote! { #name : std::sync::Arc> , }); init.extend( - quote! { #name : std::sync::Arc::new(enterprise::parking_lot::Mutex::new(#value .into())) , }, + quote! { #name : std::sync::Arc::new(enterprise::parking_lot::RwLock::new(#value .into())) , }, ); } @@ -64,8 +72,17 @@ pub fn build(component: &Component) -> TokenStream { }); } + let altgraph = visitor.graph().altgraph(); + let dot = Dot::new(&altgraph); + { + let mut file = File::create("graph.dot").unwrap(); + write!(file, "{:?}", dot).unwrap(); + } + quote! { - use enterprise::std::List; + // use std::convert::TryFrom; + use enterprise::{Component, ValueUpdatable}; + use enterprise::std::{List}; use enterprise::stdweb::web::{INode, IElement, Node, IEventTarget}; use crate::enterprise::stdweb::unstable::TryFrom; @@ -100,6 +117,5 @@ 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 486455e..955f68c 100644 --- a/enterprise-compiler/src/model/mod.rs +++ b/enterprise-compiler/src/model/mod.rs @@ -163,6 +163,7 @@ pub type Context = BTreeMap; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ContextVar { Model(ModelValue), + LoopExpr(Id), } #[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] @@ -177,7 +178,7 @@ pub enum Rsx { Elem(Elem), Code(Context, Expr), Text(String), - ForLoop(Pat, Expr, Vec), + ForLoop(Pat, Expr, Type, Vec), #[doc(hidden)] _Nonexhaustive, @@ -188,8 +189,7 @@ impl PartialEq for Rsx { match (self, other) { (Rsx::Elem(this), Rsx::Elem(other)) => this == other, (Rsx::Code(ctx, expr), Rsx::Code(ctx2, other)) => { - ctx == ctx2 && - syn::Expr::from_adapter(expr) == syn::Expr::from_adapter(other) + ctx == ctx2 && syn::Expr::from_adapter(expr) == syn::Expr::from_adapter(other) } (Rsx::Text(this), Rsx::Text(other)) => this == other, _ => false, @@ -213,14 +213,15 @@ impl ToTokens for Rsx { let string = syn::Lit::Str(syn::LitStr::new(string.as_ref(), Span::call_site())); stream.extend(quote!(#string)); } - Rsx::ForLoop(pat, expr, inner) => { + Rsx::ForLoop(pat, expr, ty, inner) => { let expr = syn::Expr::from_adapter(expr); let pat = syn::Pat::from_adapter(pat); + let ty = syn::Type::from_adapter(ty); 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 ])); + stream.extend(quote!([ for #pat in #expr : #ty ] #inner_stream [ / for ])); } Rsx::_Nonexhaustive => unreachable!("should never be constructed"), } diff --git a/enterprise-compiler/src/utils/mod.rs b/enterprise-compiler/src/utils/mod.rs index 1f27d74..2b96b56 100644 --- a/enterprise-compiler/src/utils/mod.rs +++ b/enterprise-compiler/src/utils/mod.rs @@ -5,6 +5,24 @@ use std::collections::HashSet; use symbol::Symbol; use syn::*; +pub fn process_for_loop_pat(pat: &Pat) -> (Option, Symbol) { + let mut first = None; + let mut second = None; + if let Pat::Tuple(PatTuple { elems, .. }) = pat { + let mut iter = elems.iter(); + if elems.len() == 2 { + // first one is the key + if let Some(Pat::Ident(PatIdent { ident, .. })) = iter.next() { + first = Some(Symbol::from(ident.to_string())); + } + } + if let Some(Pat::Ident(PatIdent { ident, .. })) = iter.next() { + second = Some(Symbol::from(ident.to_string())); + } + } + (first, second.unwrap()) +} + pub fn get_pat_names(pat: &Pat) -> HashSet { let mut result = HashSet::new(); match pat { diff --git a/enterprise-compiler/src/visitor.rs b/enterprise-compiler/src/visitor.rs index 4583978..9c1992b 100644 --- a/enterprise-compiler/src/visitor.rs +++ b/enterprise-compiler/src/visitor.rs @@ -1,88 +1,31 @@ //! Visitor that traverses a model and generates code. +//! +//! Most of the code here implemetns control flow analysis use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; -use petgraph::{dot::Dot, graphmap::DiGraphMap, visit::Dfs}; +use petgraph::dot::Dot; use proc_macro2::{TokenStream, TokenTree}; use quote::ToTokens; use syn::{Expr, Type}; use syn_serde::Syn; -use crate::model::{Elem, Id, ModelValue, ModelMap, Rsx, ContextVar, TagLhs, TagRhs, TaggedRsx}; +use crate::graph::{Action as DepAction, DependencyGraph, Dfs, InnerGraph, Node as DepNode}; +use crate::model::{ + Context, ContextVar, Elem, Id, ModelMap, ModelValue, Rsx, TagLhs, TagRhs, TaggedRsx, +}; use crate::utils; -use crate::{Symbol}; - -/// A node within the dependency graph. -#[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] -pub enum DepNode { - /// An anonymous code segment - CodeSeg(Symbol), - /// This is an attribute on an element - /// - /// Not read-only - RsxAttr(Symbol, Symbol), - /// This is an "on:" attribute, representing an event handler - RsxEvent(Symbol, Symbol), - /// This is a text node (innertext) - /// - /// These are read-only - RsxSpan(Symbol), - /// This is something in the model - ModelValue(ModelValue), - /// This is an iterator (for-loop) - Iterator(Symbol), -} - -#[derive(Debug, Clone)] -pub enum DepAction { - ValueChange, - IndexChange(Symbol), - SubmitEvt, -} - -impl DepNode { - // Generates code for when this updates - fn gen_update_code(&self, updates: &mut TokenStream, update_func: &mut TokenStream) { - match self { - DepNode::ModelValue(ModelValue::Leaf(sym)) => { - let sym_name = format_ident!("{}", sym.to_string()); - let inner_lock = format_ident!("inner_lock_{}", Symbol::gensym().as_str()); - updates.extend(quote! { - let #inner_lock = self.#sym_name.clone(); - }); - update_func.extend(quote! { - enterprise::ValueUpdatable::update(#inner_lock, new_value); - }); - } - DepNode::RsxSpan(id) => { - let id_str = id.as_str(); - update_func.extend(quote! { - if let Some(target) = enterprise::stdweb::web::document().get_element_by_id(#id_str) { - target.set_text_content(&new_value.clone()); - } - }); - } - DepNode::Iterator(id) => { - let update_id = format_ident!("update_loop_{}", id.as_str()); - update_func.extend(quote! { - self.#update_id(); - }); - } - _ => (), - } - } -} - -type DependencyGraph = DiGraphMap, DepAction>; +use crate::Symbol; #[derive(Default, Debug)] pub struct Visitor { idx: u32, pub(crate) deps: DependencyGraph, model: HashMap, - code_segments: HashMap, + code_segments: HashMap, pub(crate) impl_code: TokenStream, elem_attr_map: HashMap>, + elem_evt_map: HashMap>, } impl Visitor { @@ -96,10 +39,14 @@ impl Visitor { &self.impl_code } - pub fn code_segments(&self) -> &HashMap { + pub fn code_segments(&self) -> &HashMap { &self.code_segments } + pub fn graph(&self) -> &DependencyGraph { + &self.deps + } + pub fn load_model(&mut self, model: &ModelMap) { for (key, (ty, init)) in model { let ty = syn::Type::from_adapter(ty); @@ -108,10 +55,6 @@ impl Visitor { } } - pub fn dot(&self) -> Dot<&DependencyGraph> { - Dot::new(&self.deps) - } - fn hook_attrs(&mut self, node_id: Symbol, attrs: &BTreeMap) { for (lhs, rhs) in attrs { match (lhs, rhs) { @@ -122,8 +65,11 @@ impl Visitor { if self.model.contains_key(&text_sym) { let attr_node = DepNode::RsxAttr(node_id, Symbol::from(attr)); let model_node = DepNode::ModelValue(ModelValue::Leaf(text_sym)); - self.deps - .add_edge(attr_node, model_node, DepAction::ValueChange); + self.deps.add_edge( + attr_node.clone(), + model_node.clone(), + DepAction::ValueChange, + ); self.deps .add_edge(model_node, attr_node, DepAction::ValueChange); if let Some(set) = self.elem_attr_map.get_mut(&node_id) { @@ -137,24 +83,48 @@ impl Visitor { } (TagLhs::On(evt), TagRhs::Code(expr)) => { let syn_expr = syn::Expr::from_adapter(expr); - let code_node_id = self.hook_code_segment(&syn_expr); + let unit_type = syn::parse_str::("()").unwrap(); + let code_node_id = + self.hook_code_segment(&Context::default(), &syn_expr, Some(unit_type)); // add hook from attr to the code segment let from = DepNode::RsxEvent(node_id, Symbol::from(evt)); let to = DepNode::CodeSeg(code_node_id); self.deps.add_edge(from, to, DepAction::ValueChange); + + if let Some(set) = self.elem_evt_map.get_mut(&node_id) { + set.insert(Symbol::from(evt)); + } else { + let mut set = HashSet::new(); + set.insert(Symbol::from(evt)); + self.elem_evt_map.insert(node_id, set); + } } _ => (), }; } } - fn hook_code_segment(&mut self, expr: &syn::Expr) -> Symbol { + fn hook_code_segment( + &mut self, + ctx: &Context, + expr: &syn::Expr, + return_type: Option, + ) -> Symbol { let code_node_id = Symbol::gensym(); + println!( + "INPUT[{}]: {:?} {} : {:?}", + code_node_id, + ctx, + expr.to_token_stream(), + return_type + ); - // see if we need to parse i@, e@ + // see if we need to parse i@, o@ + let mut has_io = false; + let mut input_vars = HashSet::new(); + let mut output_vars = HashSet::new(); 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, @@ -171,6 +141,7 @@ impl Visitor { let from = DepNode::ModelValue(ModelValue::Leaf(sym)); let to = DepNode::CodeSeg(code_node_id); self.deps.add_edge(from, to, DepAction::ValueChange); + input_vars.insert(sym); has_io = true; } ("o", syn::Pat::Ident(syn::PatIdent { ident, .. })) => { @@ -180,6 +151,7 @@ impl Visitor { let from = DepNode::CodeSeg(code_node_id); let to = DepNode::ModelValue(ModelValue::Leaf(sym)); self.deps.add_edge(from, to, DepAction::ValueChange); + output_vars.insert(sym); has_io = true; } _ => (), @@ -194,8 +166,71 @@ impl Visitor { } else { expr.clone() }; + // perform a rudimentary search and put them in as input vars if they aren't already in there + for sym in self.extract_model_dependencies_from_expr(&expr, &self.get_model_names()) { + if !output_vars.contains(&sym) { + input_vars.insert(sym); + has_io = true; + } + } - self.code_segments.insert(code_node_id, actual_expr.clone()); + println!("==> IO[{}]: {:?} {:?}", has_io, input_vars, output_vars); + + let code_fn = format_ident!("code_segment_{}", code_node_id.to_string()); + let mut prelude = TokenStream::new(); + // add context variables + for (id, ctxvar) in ctx.iter() { + let id = format_ident!("{}", id.to_string()); + prelude.extend(quote!(let #id =)); + match ctxvar { + ContextVar::Model(model_value) => { + // let line = Indexable::index(self.todos, key) + fn gen_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 = gen_model_value_code(val); + quote!(Indexable :: index ( #val , #idx )) + } + } + } + + let mv_code = gen_model_value_code(model_value); + prelude.extend(quote!(#mv_code ;)); + } + ContextVar::LoopExpr(id) => { + // this means put the loop expression in there + let id_str = format_ident!("code_segment_{}", id.to_string()); + prelude.extend(quote!(self . #id_str () . read () ;)); + } + } + } + // stick the model variables into the prelude + if has_io { + for (id, _) in self.model.iter() { + let id_str = format_ident!("{}", id.to_string()); + if input_vars.contains(id) { + prelude.extend(quote!(let #id_str = self . # id_str . clone () ;)); + } + if output_vars.contains(id) { + prelude.extend(quote!(let #id_str = self . # id_str . clone () ;)); + } + } + } + self.code_segments + .insert(code_node_id, quote!(#actual_expr)); + let return_type = return_type + .unwrap_or_else(|| syn::parse_str::("impl std::any::Any").unwrap()); + self.impl_code.extend(quote! { + fn #code_fn(&self) -> #return_type { + #prelude + #actual_expr . into() + } + }); // look for model references in the code segment // let names = self.get_model_names(); @@ -210,13 +245,19 @@ impl Visitor { } pub fn make_graph(&mut self, nodes: &[Rsx]) -> Vec { + self.make_graph_rec(Context::default(), nodes) + } + + fn make_graph_rec(&mut self, ctx: Context, 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)); + let tag_inner = inner + .as_ref() + .map(|inner| self.make_graph_rec(ctx.clone(), inner)); // add deps for the attributes self.hook_attrs(node_id, attrs); @@ -231,25 +272,32 @@ 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(ctx, expr) => { + Rsx::Code(_, 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); - for dep in deps { - let from = DepNode::ModelValue(ModelValue::Leaf(dep)); - let to = DepNode::RsxSpan(node_id); - self.deps.add_edge(from, to, DepAction::ValueChange); - } + let string_type = + syn::parse_str::("impl std::string::ToString").unwrap(); + let code_node_id = self.hook_code_segment(&ctx, &syn_expr, Some(string_type)); - TaggedRsx::Code(node_id, ctx.clone(), Box::new(syn_expr.clone().to_adapter())) + // let names = self.get_model_names(); + // let deps = self.extract_model_dependencies_from_expr(&syn_expr, &names); + // for dep in deps { + // let from = DepNode::ModelValue(ModelValue::Leaf(dep)); + // let to = DepNode::RsxSpan(node_id); + // self.deps.add_edge(from, to, DepAction::ValueChange); + // } + + TaggedRsx::Code( + 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 // The for-loop should exist by itself in the dep tree // Everything inside the for loop is a dependency - Rsx::ForLoop(pat, expr, inner) => { + Rsx::ForLoop(pat, expr, ty, inner) => { let syn_pat = syn::Pat::from_adapter(&pat); - let new_inner = self.make_graph(inner.as_ref()); let mut names = self.get_model_names(); names.extend(utils::get_pat_names(&syn_pat)); @@ -257,12 +305,27 @@ impl Visitor { // figure out which variables in the iterator that it depends on // for example, [for x in y] depends on y. // This says that whenever something in y changes, the iterator should also change - let syn_expr = Syn::from_adapter(&*expr); - let code_node_id = self.hook_code_segment(&syn_expr); + let syn_expr = syn::Expr::from_adapter(expr); + let syn_ty = syn::Type::from_adapter(ty); + let wrapped = + syn::parse2::(quote!(Arc < RwLock < #syn_ty > >)).unwrap(); + let code_node_id = self.hook_code_segment(&ctx, &syn_expr, Some(wrapped)); let from = DepNode::CodeSeg(code_node_id); let to = DepNode::Iterator(node_id); self.deps.add_edge(from, to, DepAction::ValueChange); + let ctx = { + let mut context = ctx.clone(); + let (key, loopvar) = utils::process_for_loop_pat(&syn_pat); + // let key = key.unwrap_or_else(|| Symbol::gensym()); + // context.insert(key, ContextVar::LoopKey(code_node_id)); + // context.insert(loopvar, ContextVar::Index(ContextVar::LoopExpr(code_node_id), ContextVar::LoopKey)); + context.insert(loopvar, ContextVar::LoopExpr(code_node_id)); + // get the actual variable name + context + }; + let new_inner = self.make_graph_rec(ctx, inner.as_ref()); + // let deps = self.extract_model_dependencies_from_expr(&syn_expr, &names); // for dep in deps { // let from = DepNode::ModelValue(ModelValue::Leaf(dep)); @@ -278,7 +341,8 @@ impl Visitor { let deps = self.extract_rsx_dependents(&child, &names); let from = DepNode::Iterator(node_id); for dep in deps { - self.deps.add_edge(from, dep, DepAction::ValueChange); + self.deps + .add_edge(from.clone(), dep, DepAction::ValueChange); } } @@ -292,6 +356,10 @@ impl Visitor { } pub fn gen_code(&mut self, nodes: &[TaggedRsx]) -> Vec { + self.gen_code_rec(nodes) + } + + fn gen_code_rec(&mut self, nodes: &[TaggedRsx]) -> Vec { let mut names = Vec::new(); for node in nodes { let node_str = node.get_id().as_str(); @@ -299,18 +367,22 @@ impl Visitor { match node { TaggedRsx::Elem(node_id, Elem { tag, inner, .. }) => { let mut updates = TokenStream::new(); + // check attrs to see which ones are bound + // once found, add event listeners that propagate changes once these are changed. if let Some(this_attrs) = self.elem_attr_map.get(node_id) { for attr in this_attrs { let starting = DepNode::RsxAttr(*node_id, *attr); - let mut dfs = Dfs::new(&self.deps, starting); 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); + let mut dfs = Dfs::new(&self.deps, &starting).unwrap(); + + while let Some(node) = dfs.next() { + if node != &starting { + node.gen_update_code(&mut updates, &mut update_func); } } updates.extend(quote! { { + // need to clone this inside so it can be moved 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(); @@ -320,6 +392,39 @@ impl Visitor { }); } } + // add event listeners for the events + if let Some(this_evts) = self.elem_evt_map.get(node_id) { + for evt in this_evts { + let evt_str = evt.to_string(); + let evt_type = match evt.as_ref() { + "submit" => { + Some(quote!(enterprise::stdweb::web::event::SubmitEvent)) + } + _ => None, + }; + if let Some(evt_type) = evt_type { + let starting = DepNode::RsxEvent(*node_id, *evt); + let mut update_func = TokenStream::new(); + let mut dfs = Dfs::new(&self.deps, &starting).unwrap(); + + while let Some(node) = dfs.next() { + if node != &starting { + node.gen_update_code(&mut updates, &mut update_func); + } + } + updates.extend(quote! { + { + // need to clone this inside so it can be moved + let inner_el = el.clone(); + el.add_event_listener(move |evt: #evt_type| { + let new_value = enterprise::stdweb::web::html_element::InputElement::try_from(inner_el.clone()).unwrap().raw_value(); + #update_func + }); + } + }); + } + } + } let elem_as_str = format!("{}", node.to_token_stream()); self.impl_code.extend(quote! { #[doc = #elem_as_str] @@ -331,44 +436,16 @@ impl Visitor { } }); if let Some(inner) = inner { - self.gen_code(inner); + self.gen_code_rec(inner); } names.push(format!("{}", make_node_id)); } - 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 )) - } - } - } - } - } - } + TaggedRsx::Code(code_node_id, ctx, expr) => { + // let expr = syn::Expr::from_adapter(expr); + // // let code_id = format_ident!("code_{}", node_str); + // let code_node_id = self.hook_code_segment(&ctx, &expr); self.impl_code.extend(quote! { - /// Actual code - fn #code_id(&self) -> impl std::string::ToString { - #expr - } - /// Code fn #make_node_id(&self) -> enterprise::stdweb::web::Node { let el = enterprise::stdweb::web::document().create_element("span").expect("shouldn't fail"); @@ -382,7 +459,7 @@ impl Visitor { self.impl_code.extend(quote! { /// Text node fn #make_node_id(&self) -> enterprise::stdweb::web::Node { - let text = enterprise::widgets::Text::new(#literal); + let text = enterprise::std::widgets::Text::new(#literal); text.render() } }); @@ -394,7 +471,7 @@ impl Visitor { // Generate code for the initial creation of the loop let mut func_calls = TokenStream::new(); - for name in self.gen_code(&inner) { + for name in self.gen_code_rec(&inner) { let name = format_ident!("{}", name); func_calls.extend(quote! { let sub = self.#name(); @@ -446,7 +523,7 @@ impl Visitor { .collect::>(); result.extend(code_deps); } - Rsx::ForLoop(_pat, _expr, inner) => { + Rsx::ForLoop(_pat, _expr, _ty, inner) => { for child in inner.iter() { self.extract_rsx_dependents(child, names); } diff --git a/enterprise-macros/Cargo.toml b/enterprise-macros/Cargo.toml index c6f248f..20901fe 100644 --- a/enterprise-macros/Cargo.toml +++ b/enterprise-macros/Cargo.toml @@ -15,11 +15,12 @@ path = "src/tests.rs" proptest = "0.9.5" [dependencies] +enterprise = { path = ".." } +enterprise-compiler = { path = "../enterprise-compiler" } proc-macro2 = { version = "1.0.7", features = ["span-locations"] } quote = "1.0.2" -thiserror = "1.0.9" -symbol = { path = "../symbol" } -enterprise-compiler = { path = "../enterprise-compiler" } -syn-serde = { path = "../syn-serde" } -syn = { version = "1.0.14", features = ["extra-traits", "full"] } serde_json = "1.0.48" +symbol = { path = "../symbol" } +syn = { version = "1.0.14", features = ["extra-traits", "full"] } +syn-serde = { path = "../syn-serde" } +thiserror = "1.0.9" diff --git a/enterprise-macros/src/parser.rs b/enterprise-macros/src/parser.rs index 11fb877..240a9e6 100644 --- a/enterprise-macros/src/parser.rs +++ b/enterprise-macros/src/parser.rs @@ -183,7 +183,7 @@ impl Visitor { fn consume_view(&mut self) -> Result, ParseError> { enum Container { Tag(String, BTreeMap), - ForLoop(Pat, Expr), + ForLoop(Pat, Expr, Type), } let mut rsx_parser = RsxParser::new(self.0.clone()); @@ -227,18 +227,24 @@ impl Visitor { } } RsxToken::Code(expr) => { - result.push(Rsx::Code(expr.to_adapter())); + result.push(Rsx::Code(BTreeMap::new(), expr.to_adapter())); } RsxToken::Str(string) => { result.push(Rsx::Text(string)); } - RsxToken::OpeningFor(pat, expr) => { - tag_stack.push((Container::ForLoop(pat, expr), result.clone())); + RsxToken::OpeningFor(pat, expr, ty) => { + tag_stack.push((Container::ForLoop(pat, expr, ty), result.clone())); } RsxToken::ClosingFor => { - if let Some((Container::ForLoop(pat, expr), mut last_result)) = tag_stack.pop() + if let Some((Container::ForLoop(pat, expr, ty), mut last_result)) = + tag_stack.pop() { - last_result.push(Rsx::ForLoop(pat.to_adapter(), expr.to_adapter(), result)); + last_result.push(Rsx::ForLoop( + pat.to_adapter(), + expr.to_adapter(), + ty.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 480bacf..80e1b72 100644 --- a/enterprise-macros/src/rsx.rs +++ b/enterprise-macros/src/rsx.rs @@ -6,8 +6,10 @@ use enterprise_compiler::model::{TagLhs, TagRhs}; use proc_macro2::{token_stream::IntoIter, Delimiter, Ident, TokenStream, TokenTree}; use symbol::Symbol; use syn::{ + braced, parse::{Parse, ParseStream}, - Expr, Lit, Pat, Result as SynResult, Token, + token::Brace, + Expr, Lit, Pat, Result as SynResult, Token, Type, }; use syn_serde::Syn; @@ -161,16 +163,23 @@ impl RsxParser { kw_for: Token![for], pat: Pat, kw_in: Token![in], + bruh: Brace, expr: Expr, + colon: Token![:], + ty: Type, } impl Parse for ForLoopHeader { fn parse(input: ParseStream) -> SynResult { + let expr; Ok(ForLoopHeader { kw_for: input.parse()?, pat: input.parse()?, kw_in: input.parse()?, - expr: input.parse()?, + bruh: braced!(expr in input), + expr: expr.parse()?, + colon: input.parse()?, + ty: input.parse()?, }) } } @@ -179,7 +188,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(for_loop.pat, for_loop.expr))) + Ok(Some(RsxToken::OpeningFor( + for_loop.pat, + for_loop.expr, + for_loop.ty, + ))) } Some(TokenTree::Punct(punct)) if punct.as_char() == '/' => { stream.next(); @@ -216,7 +229,7 @@ pub(crate) enum RsxToken { ClosingTag(Symbol), Str(String), Code(Expr), - OpeningFor(Pat, Expr), + OpeningFor(Pat, Expr, Type), ClosingFor, } diff --git a/enterprise-macros/src/tests.rs b/enterprise-macros/src/tests.rs index 6013653..b07b7fa 100644 --- a/enterprise-macros/src/tests.rs +++ b/enterprise-macros/src/tests.rs @@ -16,47 +16,34 @@ component! { } view { - +
    - [for (key, line) in todos] -
  • {line} "[x]"
  • + [for (key, line) in {todos} : List] +
  • {line}
  • [/for]
} } } -// pub mod enterprise { -// include!("../../ouais.rs"); -// } +pub mod enterprise { + include!("../../ouais.rs"); +} fn main() { let component: Component = serde_json::from_str(TodoMVC.as_ref()).unwrap(); - let mut visitor = Visitor::new(); - visitor.load_model(&component.model); - visitor.make_graph(&component.view); + // let mut visitor = Visitor::new(); + // visitor.load_model(&component.model); + // 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 dot = visitor.dot(); - { - let mut file = File::create("graph.dot").unwrap(); - write!(file, "{:?}", dot).unwrap(); - } - + // 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(); } - - println!(); - println!("Code segments:"); - for (l, r) in visitor.code_segments() { - println!("{:?}: {}", l, r.to_token_stream()); - } } diff --git a/examples/todomvc/src/build.rs b/examples/todomvc/src/build.rs index d1f5e90..3ea3cd4 100644 --- a/examples/todomvc/src/build.rs +++ b/examples/todomvc/src/build.rs @@ -9,10 +9,10 @@ component! { } view { - +
    - [for (key, line) in todos] -
  • {line} "[x]"
  • + [for (key, line) in {todos} : List] +
  • {line}
  • [/for]
} diff --git a/src/lib.rs b/src/lib.rs index f81ad87..4f79168 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ mod forloop; use rust_std::sync::Arc; -use parking_lot::Mutex; +use parking_lot::RwLock; use stdweb::web::Node; pub use crate::backend::{Backend, Web}; @@ -37,12 +37,19 @@ macro_rules! enterprise_mod { } } +#[macro_export] +macro_rules! set { + ($name:ident = $expr:expr) => { + ValueUpdatable::update($name, $expr.into()) + }; +} + pub trait ValueUpdatable { - fn update(_: Arc>, _: String); + fn update(_: Arc>, _: Self); } impl ValueUpdatable for String { - fn update(value_ref: Arc>, new_value: String) { - *value_ref.lock() = new_value; + fn update(value_ref: Arc>, new_value: String) { + *value_ref.write() = new_value; } } diff --git a/src/std/list.rs b/src/std/list.rs index d6eef35..1174da4 100644 --- a/src/std/list.rs +++ b/src/std/list.rs @@ -2,11 +2,15 @@ use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; use std::ops::Index; +use std::sync::Arc; use std::ptr::NonNull; +use parking_lot::RwLock; use symbol::Symbol; +use crate::ValueUpdatable; + /// A list that guarantees: /// - O(1) insertion /// - O(1) deletion @@ -71,7 +75,7 @@ impl Clone for List { fn clone(&self) -> Self { let mut new_list = List::new(); for item in self.iter() { - new_list.insert(item.clone()); + new_list.push(item.clone()); } new_list } @@ -144,8 +148,14 @@ impl List { } } + /// Immutable insert + pub fn with(mut self, item: T) -> List { + self.push(item); + self + } + /// Inserts the specified item into the list - pub fn insert(&mut self, item: T) -> Symbol { + pub fn push(&mut self, item: T) -> Symbol { let node = Node { prev: None, next: None, @@ -192,6 +202,14 @@ impl List { } } +impl ValueUpdatable for List { + fn update(value_ref: Arc>, new_value: Self) { + // TODO: merge? + let mut locked = value_ref.write(); + *locked = new_value; + } +} + #[cfg(test)] mod tests { use super::List;