asdf
This commit is contained in:
parent
ded42bef64
commit
3d8bc75e6f
12 changed files with 338 additions and 193 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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)",
|
||||
|
|
|
@ -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<B> {
|
||||
|
@ -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<enterprise::parking_lot::Mutex<#ty>> , });
|
||||
model.extend(quote! { #name : std::sync::Arc<enterprise::parking_lot::RwLock<#ty>> , });
|
||||
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<str>, code: impl AsRef<str>) {
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -163,6 +163,7 @@ pub type Context = BTreeMap<Id, ContextVar>;
|
|||
#[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<Rsx>),
|
||||
Code(Context, Expr),
|
||||
Text(String),
|
||||
ForLoop(Pat, Expr, Vec<Rsx>),
|
||||
ForLoop(Pat, Expr, Type, Vec<Rsx>),
|
||||
|
||||
#[doc(hidden)]
|
||||
_Nonexhaustive,
|
||||
|
@ -188,8 +189,7 @@ impl PartialEq<Rsx> 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"),
|
||||
}
|
||||
|
|
|
@ -5,6 +5,24 @@ use std::collections::HashSet;
|
|||
use symbol::Symbol;
|
||||
use syn::*;
|
||||
|
||||
pub fn process_for_loop_pat(pat: &Pat) -> (Option<Symbol>, 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<Symbol> {
|
||||
let mut result = HashSet::new();
|
||||
match pat {
|
||||
|
|
|
@ -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<Ptr<DepNode>, DepAction>;
|
||||
use crate::Symbol;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Visitor {
|
||||
idx: u32,
|
||||
pub(crate) deps: DependencyGraph,
|
||||
model: HashMap<Id, (Type, Expr)>,
|
||||
code_segments: HashMap<Id, Expr>,
|
||||
code_segments: HashMap<Id, TokenStream>,
|
||||
pub(crate) impl_code: TokenStream,
|
||||
elem_attr_map: HashMap<Id, HashSet<Id>>,
|
||||
elem_evt_map: HashMap<Id, HashSet<Id>>,
|
||||
}
|
||||
|
||||
impl Visitor {
|
||||
|
@ -96,10 +39,14 @@ impl Visitor {
|
|||
&self.impl_code
|
||||
}
|
||||
|
||||
pub fn code_segments(&self) -> &HashMap<Id, Expr> {
|
||||
pub fn code_segments(&self) -> &HashMap<Id, TokenStream> {
|
||||
&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<TagLhs, TagRhs>) {
|
||||
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::<syn::Type>("()").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<Type>,
|
||||
) -> 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::<syn::Type>("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<TaggedRsx> {
|
||||
self.make_graph_rec(Context::default(), nodes)
|
||||
}
|
||||
|
||||
fn make_graph_rec(&mut self, ctx: Context, nodes: &[Rsx]) -> Vec<TaggedRsx> {
|
||||
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::<syn::Type>("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::<syn::Type>(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<String> {
|
||||
self.gen_code_rec(nodes)
|
||||
}
|
||||
|
||||
fn gen_code_rec(&mut self, nodes: &[TaggedRsx]) -> Vec<String> {
|
||||
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::<HashSet<_>>();
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -183,7 +183,7 @@ impl Visitor {
|
|||
fn consume_view(&mut self) -> Result<Vec<Rsx>, ParseError> {
|
||||
enum Container {
|
||||
Tag(String, BTreeMap<TagLhs, TagRhs>),
|
||||
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);
|
||||
|
|
|
@ -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<Self> {
|
||||
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::<ForLoopHeader>(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,
|
||||
}
|
||||
|
||||
|
|
|
@ -16,47 +16,34 @@ component! {
|
|||
}
|
||||
|
||||
view {
|
||||
<input bind:value="value" on:submit={|o@todos| { todos.push(value); value = ""; }} />
|
||||
<input bind:value="value" on:submit={|o@todos, o@value| { set!(todos = todos.with(value)); set!(value = ""); }} />
|
||||
<ul>
|
||||
[for (key, line) in todos]
|
||||
<li>{line} <a on:click={|o@todos| { todos.remove(key); }}>"[x]"</a></li>
|
||||
[for (key, line) in {todos} : List<String>]
|
||||
<li>{line}</li>
|
||||
[/for]
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ component! {
|
|||
}
|
||||
|
||||
view {
|
||||
<input bind:value="value" on:submit={|o@todos| { todos.push(value); value = ""; }} />
|
||||
<input bind:value="value" on:submit={|o@todos, o@value| { set!(todos = todos.with(value)); set!(value = ""); }} />
|
||||
<ul>
|
||||
[for (key, line) in todos]
|
||||
<li>{line} <a on:click={|o@todos| { todos.remove(key); }}>"[x]"</a></li>
|
||||
[for (key, line) in {todos} : List<String>]
|
||||
<li>{line}</li>
|
||||
[/for]
|
||||
</ul>
|
||||
}
|
||||
|
|
15
src/lib.rs
15
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<Mutex<Self>>, _: String);
|
||||
fn update(_: Arc<RwLock<Self>>, _: Self);
|
||||
}
|
||||
|
||||
impl ValueUpdatable for String {
|
||||
fn update(value_ref: Arc<Mutex<Self>>, new_value: String) {
|
||||
*value_ref.lock() = new_value;
|
||||
fn update(value_ref: Arc<RwLock<Self>>, new_value: String) {
|
||||
*value_ref.write() = new_value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T: Clone> Clone for List<T> {
|
|||
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<T> List<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Immutable insert
|
||||
pub fn with(mut self, item: T) -> List<T> {
|
||||
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<T> List<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> ValueUpdatable for List<T> {
|
||||
fn update(value_ref: Arc<RwLock<Self>>, new_value: Self) {
|
||||
// TODO: merge?
|
||||
let mut locked = value_ref.write();
|
||||
*locked = new_value;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::List;
|
||||
|
|
Loading…
Reference in a new issue