From 0e95823708898ccbd754e912ab8d9242b5276bfd Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 24 Feb 2020 01:43:28 -0600 Subject: [PATCH] ouais --- enterprise-compiler/src/lib.rs | 2 +- enterprise-compiler/src/model/mod.rs | 4 +- enterprise-compiler/src/utils/mod.rs | 28 ++ .../src/{ => utils}/tuple_map.rs | 0 enterprise-compiler/src/visitor.rs | 293 ++++++++++++++---- enterprise-macros/src/tests.rs | 29 +- src/std/list.rs | 8 + 7 files changed, 302 insertions(+), 62 deletions(-) create mode 100644 enterprise-compiler/src/utils/mod.rs rename enterprise-compiler/src/{ => utils}/tuple_map.rs (100%) diff --git a/enterprise-compiler/src/lib.rs b/enterprise-compiler/src/lib.rs index 633471c..0fae3cc 100644 --- a/enterprise-compiler/src/lib.rs +++ b/enterprise-compiler/src/lib.rs @@ -4,7 +4,7 @@ extern crate quote; extern crate serde_derive; pub mod model; -mod tuple_map; +mod utils; mod visitor; use std::env; diff --git a/enterprise-compiler/src/model/mod.rs b/enterprise-compiler/src/model/mod.rs index 44db50d..9f0aff7 100644 --- a/enterprise-compiler/src/model/mod.rs +++ b/enterprise-compiler/src/model/mod.rs @@ -30,7 +30,7 @@ pub fn convert_map(map: BTreeMap) -> BTreeMap #[derive(Debug, Serialize, Deserialize)] pub struct Component { pub name: String, - #[serde(with = "crate::tuple_map")] + #[serde(with = "crate::utils::tuple_map")] pub model: ModelMap, pub view: Vec, } @@ -134,7 +134,7 @@ impl ToTokens for TagRhs { #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct Elem { pub tag: String, - #[serde(with = "crate::tuple_map")] + #[serde(with = "crate::utils::tuple_map")] pub attrs: BTreeMap, pub inner: Option>, } diff --git a/enterprise-compiler/src/utils/mod.rs b/enterprise-compiler/src/utils/mod.rs new file mode 100644 index 0000000..1f27d74 --- /dev/null +++ b/enterprise-compiler/src/utils/mod.rs @@ -0,0 +1,28 @@ +pub mod tuple_map; + +use std::collections::HashSet; + +use symbol::Symbol; +use syn::*; + +pub fn get_pat_names(pat: &Pat) -> HashSet { + let mut result = HashSet::new(); + match pat { + Pat::Box(PatBox { pat, .. }) => { + result.extend(get_pat_names(pat)); + } + Pat::Ident(PatIdent { ident, subpat, .. }) => { + result.insert(Symbol::from(ident.to_string())); + if let Some((_, boxpat)) = subpat { + result.extend(get_pat_names(boxpat)); + } + } + Pat::Tuple(PatTuple { elems, .. }) => { + for pat in elems.iter() { + result.extend(get_pat_names(pat)); + } + } + _ => (), + } + result +} diff --git a/enterprise-compiler/src/tuple_map.rs b/enterprise-compiler/src/utils/tuple_map.rs similarity index 100% rename from enterprise-compiler/src/tuple_map.rs rename to enterprise-compiler/src/utils/tuple_map.rs diff --git a/enterprise-compiler/src/visitor.rs b/enterprise-compiler/src/visitor.rs index b9bc790..331cd35 100644 --- a/enterprise-compiler/src/visitor.rs +++ b/enterprise-compiler/src/visitor.rs @@ -1,7 +1,6 @@ //! Visitor that traverses a model and generates code. -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use petgraph::{dot::Dot, graphmap::DiGraphMap, visit::Dfs}; use proc_macro2::{TokenStream, TokenTree}; @@ -10,29 +9,48 @@ use syn::{Expr, Type}; use syn_serde::Syn; use crate::model::{Elem, Id, ModelMap, 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(Symbol), + ModelValue(ModelValue), /// This is an iterator (for-loop) Iterator(Symbol), } +#[derive(Debug, Clone)] +pub enum DepAction { + ValueChange, + IndexChange(Symbol), + 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) { match self { - DepNode::ModelValue(sym) => { + 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! { @@ -56,22 +74,27 @@ impl DepNode { } }); } + DepNode::Iterator(id) => { + let update_id = format_ident!("update_loop_{}", id.as_str()); + update_func.extend(quote! { + self.#update_id(); + }); + } _ => (), } } } -type DependencyGraph = DiGraphMap; +type DependencyGraph = DiGraphMap; #[derive(Default, Debug)] pub struct Visitor { idx: u32, pub(crate) deps: DependencyGraph, model: HashMap, + code_segments: HashMap, pub(crate) impl_code: TokenStream, elem_attr_map: HashMap>, - // symbol maps - // model_bimap: BiHashMap, } impl Visitor { @@ -85,19 +108,115 @@ impl Visitor { &self.impl_code } + pub fn code_segments(&self) -> &HashMap { + &self.code_segments + } + pub fn load_model(&mut self, model: &ModelMap) { for (key, (ty, init)) in model { - let ty = Syn::from_adapter(&*ty); - let init = Syn::from_adapter(&*init); + let ty = syn::Type::from_adapter(ty); + let init = syn::Expr::from_adapter(init); self.model.insert(key.clone(), (ty, init)); } - // self.model.extend(model.clone()); } 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) { + // If the left-hand side contains bind:attr="name", put that attribute as a dependency of name + (TagLhs::Bind(attr), TagRhs::Text(text)) => { + 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(ModelValue::Leaf(text_sym)); + self.deps + .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 { + let mut set = HashSet::new(); + set.insert(Symbol::from(attr)); + self.elem_attr_map.insert(node_id, set); + } + } + } + (TagLhs::On(evt), TagRhs::Code(expr)) => { + let syn_expr = syn::Expr::from_adapter(expr); + let code_node_id = self.hook_code_segment(&syn_expr); + + // 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); + } + _ => (), + }; + } + } + + fn hook_code_segment(&mut self, expr: &syn::Expr) -> Symbol { + let code_node_id = Symbol::gensym(); + + // see if we need to parse i@, e@ + 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 { + let bind = ident.to_string(); + match (bind.as_ref(), &(**subpat)) { + ("i", syn::Pat::Ident(syn::PatIdent { ident, .. })) => { + // input variable + // this means the code segment depends on this variable + let sym = Symbol::from(ident.to_string()); + let from = DepNode::ModelValue(ModelValue::Leaf(sym)); + let to = DepNode::CodeSeg(code_node_id); + self.deps.add_edge(from, to, DepAction::ValueChange); + has_io = true; + } + ("o", syn::Pat::Ident(syn::PatIdent { ident, .. })) => { + // output variable + // this means the code segment propagates updates to the variable + let sym = Symbol::from(ident.to_string()); + let from = DepNode::CodeSeg(code_node_id); + let to = DepNode::ModelValue(ModelValue::Leaf(sym)); + self.deps.add_edge(from, to, DepAction::ValueChange); + has_io = true; + } + _ => (), + } + } + } + if has_io { + (**body).clone() + } else { + expr.clone() + } + } else { + expr.clone() + }; + + self.code_segments.insert(code_node_id, expr.clone()); + + // look for model references in the code segment + // let names = self.get_model_names(); + // let deps = self.extract_model_dependencies_from_expr(&expr, &names); + // for dep in deps { + // let from = DepNode::ModelValue(ModelValue::Leaf(dep)); + // let to = DepNode::CodeSeg(code_node_id); + // self.deps.add_edge(from, to, DepAction::ValueChange); + // } + + code_node_id + } + pub fn make_graph(&mut self, nodes: &[Rsx]) -> Vec { let mut new_nodes = Vec::new(); for node in nodes { @@ -106,26 +225,9 @@ impl Visitor { // 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 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); - } - } - } - } + + // add deps for the attributes + self.hook_attrs(node_id, attrs); TaggedRsx::Elem( node_id, Elem { @@ -135,13 +237,16 @@ 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) => { let syn_expr = Syn::from_adapter(&*expr); - let deps = self.extract_model_dependencies(&syn_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(dep); + let from = DepNode::ModelValue(ModelValue::Leaf(dep)); let to = DepNode::RsxSpan(node_id); - self.deps.add_edge(from, to, ()); + self.deps.add_edge(from, to, DepAction::ValueChange); } TaggedRsx::Code(node_id, Box::new(syn_expr.clone().to_adapter())) @@ -151,17 +256,41 @@ impl Visitor { // 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 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)); + // 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 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, ()); + let code_node_id = self.hook_code_segment(&syn_expr); + let from = DepNode::CodeSeg(code_node_id); + let to = DepNode::Iterator(node_id); + self.deps.add_edge(from, to, DepAction::ValueChange); + + // 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::Iterator(node_id); + // self.deps + // .add_edge(from, to, DepAction::IndexChange(node_id)); + // } + + // all of its children are dependencies of the iterator + // Every time the iterator updates, the children update + // - 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); + } } + TaggedRsx::ForLoop(node_id, pat.clone(), expr.clone(), new_inner) } unknown => unimplemented!("unknown rsx: {:?}", unknown), @@ -187,7 +316,6 @@ impl Visitor { while let Some(nx) = dfs.next(&self.deps) { if nx != starting { nx.gen_update_code( - // &self.model_bimap, &mut updates, &mut update_func, ); @@ -224,11 +352,12 @@ impl Visitor { let expr = syn::Expr::from_adapter(expr); let code_id = format_ident!("code_{}", node_str); self.impl_code.extend(quote! { + /// Actual code fn #code_id(&self) -> impl std::string::ToString { #expr } - #[inline] + /// 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"); @@ -240,26 +369,38 @@ impl Visitor { } TaggedRsx::Text(_, literal) => { self.impl_code.extend(quote! { - #[inline] + /// Text node 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 update_loop_id = format_ident!("update_loop_{}", node_str); + + // Generate code for the initial creation of the loop let mut func_calls = TokenStream::new(); for name in self.gen_code(&inner) { let name = format_ident!("{}", name); - func_calls.extend(quote! { self.#name(); }); + func_calls.extend(quote! { + let sub = self.#name(); + el.append_child(sub); + }); } self.impl_code.extend(quote! { - #[inline] + /// 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() + } + + /// Update for-loop + fn #update_loop_id(&self) { + } }); names.push(format!("{}", init_loop_id)); @@ -270,19 +411,67 @@ impl Visitor { names } + /// Get the names that exist in the model right now + fn get_model_names(&self) -> HashSet { + self.model.keys().cloned().collect() + } + + 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) => { + if let Some(inner) = &elem.inner { + for child in inner.iter() { + self.extract_rsx_dependents(child, names); + } + } + } + Rsx::Code(code) => { + let code = syn::Expr::from_adapter(code); + let code_deps = self + .extract_model_dependencies_from_expr(&code, names) + .into_iter() + .map(|sym| DepNode::ModelValue(ModelValue::Leaf(sym))) + .collect::>(); + result.extend(code_deps); + } + Rsx::ForLoop(_pat, _expr, inner) => { + for child in inner.iter() { + self.extract_rsx_dependents(child, names); + } + } + _ => (), + } + println!("<<<<<<<<<<<<{:?}", result); + result + } + /// This is using a really dumb heuristic - fn extract_model_dependencies(&self, expr: &Expr) -> HashSet { + fn extract_model_dependencies_from_expr( + &self, + expr: &Expr, + names: &HashSet, + ) -> HashSet { + println!("Extracting {}", expr.to_token_stream()); let tokens = expr.to_token_stream(); let mut result = HashSet::new(); - for token in tokens.into_iter() { - if let TokenTree::Ident(ident) = token { - // if let Some(id) = self.model_bimap.get_by_right(&ident.to_string()) { - let sym = Symbol::from(ident.to_string()); - if self.model.contains_key(&sym) { - result.insert(sym); + let mut queue = tokens.into_iter().collect::>(); + while !queue.is_empty() { + let token = queue.pop_front().unwrap(); + // for token in tokens.into_iter() { + match token { + TokenTree::Ident(ident) => { + let sym = Symbol::from(ident.to_string()); + if names.contains(&sym) { + result.insert(sym); + } } - // result.insert(format!("{}", ident)); + TokenTree::Group(group) => { + queue.extend(group.stream().into_iter()); + } + _ => (), } } result diff --git a/enterprise-macros/src/tests.rs b/enterprise-macros/src/tests.rs index 63a1288..ea0ecee 100644 --- a/enterprise-macros/src/tests.rs +++ b/enterprise-macros/src/tests.rs @@ -6,6 +6,7 @@ use std::io::Write; use enterprise_compiler::model::Component; use enterprise_compiler::Visitor; +use quote::ToTokens; component! { component TodoMVC { @@ -15,10 +16,10 @@ component! { } view { - +
    - [for (key, todo) in todos] -
  • {todo} "[x]"
  • + [for (key, line) in todos] +
  • {line} "[x]"
  • [/for]
} @@ -30,14 +31,28 @@ fn main() { let mut visitor = Visitor::new(); visitor.load_model(&component.model); - let tagged_dom = visitor.make_graph(&component.view); + visitor.make_graph(&component.view); // println!("Tagged dom: {:?}", tagged_dom); - let toplevel_names = visitor.gen_code(&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(); + } + let all_code = enterprise_compiler::build(&component); - let mut file = File::create("ouais.rs").unwrap(); - write!(file, "{}", all_code).unwrap(); + { + 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/src/std/list.rs b/src/std/list.rs index fb1e1c1..d6eef35 100644 --- a/src/std/list.rs +++ b/src/std/list.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; +use std::ops::Index; use std::ptr::NonNull; @@ -93,6 +94,13 @@ impl Debug for List { } } +impl Index for List { + type Output = T; + fn index(&self, key: Symbol) -> &Self::Output { + self.get(&key).unwrap() + } +} + impl List { /// Creates a new List pub fn new() -> Self {