This commit is contained in:
Michael Zhang 2020-02-24 01:43:28 -06:00
parent fbd3d8ffd2
commit 0e95823708
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
7 changed files with 302 additions and 62 deletions

View file

@ -4,7 +4,7 @@ extern crate quote;
extern crate serde_derive;
pub mod model;
mod tuple_map;
mod utils;
mod visitor;
use std::env;

View file

@ -30,7 +30,7 @@ pub fn convert_map<T: Ord>(map: BTreeMap<T, (syn::Type, syn::Expr)>) -> 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<Rsx>,
}
@ -134,7 +134,7 @@ impl ToTokens for TagRhs {
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Elem<T> {
pub tag: String,
#[serde(with = "crate::tuple_map")]
#[serde(with = "crate::utils::tuple_map")]
pub attrs: BTreeMap<TagLhs, TagRhs>,
pub inner: Option<Vec<T>>,
}

View file

@ -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<Symbol> {
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
}

View file

@ -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<DepNode, ()>;
type DependencyGraph = DiGraphMap<DepNode, DepAction>;
#[derive(Default, Debug)]
pub struct Visitor {
idx: u32,
pub(crate) deps: DependencyGraph,
model: HashMap<Id, (Type, Expr)>,
code_segments: HashMap<Id, Expr>,
pub(crate) impl_code: TokenStream,
elem_attr_map: HashMap<Id, HashSet<Id>>,
// symbol maps
// model_bimap: BiHashMap<Id, String>,
}
impl Visitor {
@ -85,19 +108,115 @@ impl Visitor {
&self.impl_code
}
pub fn code_segments(&self) -> &HashMap<Id, Expr> {
&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<TagLhs, TagRhs>) {
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<TaggedRsx> {
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<Symbol> {
self.model.keys().cloned().collect()
}
fn extract_rsx_dependents(&self, rsx: &Rsx, names: &HashSet<Symbol>) -> HashSet<DepNode> {
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::<HashSet<_>>();
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<Symbol> {
fn extract_model_dependencies_from_expr(
&self,
expr: &Expr,
names: &HashSet<Symbol>,
) -> HashSet<Symbol> {
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::<VecDeque<_>>();
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

View file

@ -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 {
<input bind:value="value" on:submit={|| { todos.push(todo); }} />
<input bind:value="value" on:submit={|i@value, o@todos| { todos.push(value); }} />
<ul>
[for (key, todo) in todos]
<li>{todo} <a on:click={|_| { todos.remove(key); }}>"[x]"</a></li>
[for (key, line) in todos]
<li>{line} <a on:click={|o@todos| { todos.remove(key); }}>"[x]"</a></li>
[/for]
</ul>
}
@ -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());
}
}

View file

@ -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<T: Debug> Debug for List<T> {
}
}
impl<T> Index<Symbol> for List<T> {
type Output = T;
fn index(&self, key: Symbol) -> &Self::Output {
self.get(&key).unwrap()
}
}
impl<T> List<T> {
/// Creates a new List<T>
pub fn new() -> Self {