ok
This commit is contained in:
parent
0daf13f066
commit
fbd3d8ffd2
23 changed files with 425 additions and 101 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -103,7 +103,7 @@ dependencies = [
|
|||
"serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"symbol 0.1.0",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn-serde 0.2.0",
|
||||
]
|
||||
|
||||
|
@ -117,7 +117,7 @@ dependencies = [
|
|||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"symbol 0.1.0",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn-serde 0.2.0",
|
||||
"thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -522,7 +522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -574,7 +574,7 @@ dependencies = [
|
|||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -589,7 +589,7 @@ dependencies = [
|
|||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -619,7 +619,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -636,7 +636,7 @@ dependencies = [
|
|||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -667,7 +667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -722,7 +722,7 @@ dependencies = [
|
|||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -742,7 +742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -842,7 +842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum stdweb-internal-macros 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
|
||||
"checksum stdweb-internal-runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"
|
||||
"checksum syn 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a0294dc449adc58bb6592fff1a23d3e5e6e235afc6a0ffca2657d19e7bbffe5"
|
||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||
"checksum thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db"
|
||||
"checksum thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4"
|
||||
|
|
|
@ -4,12 +4,14 @@ version = "0.1.0"
|
|||
authors = ["Michael Zhang <iptq@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "0.9.5"
|
||||
proptest-derive = "0.1.2"
|
||||
|
||||
[dependencies]
|
||||
bimap = "0.4.0"
|
||||
petgraph = "0.5.0"
|
||||
proc-macro2 = "1.0.8"
|
||||
proptest = "0.9.5"
|
||||
proptest-derive = "0.1.2"
|
||||
quote = "1.0.2"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.104"
|
||||
|
|
|
@ -12,28 +12,36 @@ use std::fs::File;
|
|||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub use crate::visitor::Visitor;
|
||||
|
||||
use crate::model::Component;
|
||||
use crate::visitor::Visitor;
|
||||
use proc_macro2::TokenStream;
|
||||
use symbol::Symbol;
|
||||
|
||||
pub fn build(
|
||||
// name: impl AsRef<str>,
|
||||
// datamodel: &HashMap<String, String>,
|
||||
// datainit: &HashMap<String, String>,
|
||||
// dom: &[Rsx],
|
||||
component: &Component,
|
||||
) -> TokenStream {
|
||||
pub fn build(component: &Component) -> TokenStream {
|
||||
let name = &component.name;
|
||||
|
||||
let mut visitor = Visitor::new();
|
||||
visitor.load_model(&component.model);
|
||||
let new_dom = visitor.make_graph(&component.view);
|
||||
let toplevel_names = visitor.gen_code(&new_dom);
|
||||
|
||||
// let graph: Graph<_, _, _> = visitor.deps.clone().into_graph();
|
||||
// println!("{:?}", Dot::new(&graph));
|
||||
let tagged_dom = visitor.make_graph(&component.view);
|
||||
println!("New DOM: {:?}", tagged_dom);
|
||||
let toplevel_names = visitor.gen_code(&tagged_dom);
|
||||
|
||||
// output the "model"
|
||||
// looks a little bit like
|
||||
// struct Name<B> {
|
||||
// _b: PhantomData<B>,
|
||||
// name: Type,
|
||||
// }
|
||||
//
|
||||
// impl<B: Backend> Name {
|
||||
// pub fn new() -> Self {
|
||||
// Self {
|
||||
// _b: PhantomData::new(),
|
||||
// name: value,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
let name = format_ident!("{}", name);
|
||||
let mut model = TokenStream::new();
|
||||
let mut init = TokenStream::new();
|
||||
|
@ -66,7 +74,7 @@ pub fn build(
|
|||
#model
|
||||
}
|
||||
|
||||
impl<B> #name<B> {
|
||||
impl<B: enterprise::Backend> #name<B> {
|
||||
pub fn new(_: &B) -> Self {
|
||||
#name {
|
||||
_b: std::marker::PhantomData::default(),
|
||||
|
@ -90,5 +98,6 @@ 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();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
//! The enterprise DSL model.
|
||||
|
||||
#[cfg(test)]
|
||||
mod props;
|
||||
#[cfg(test)]
|
||||
pub use self::props::*;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::{self, FromIterator};
|
||||
|
@ -6,9 +11,7 @@ use std::iter::{self, FromIterator};
|
|||
use proc_macro2::{Span, TokenStream, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use symbol::Symbol;
|
||||
use syn_serde::{Expr, Syn, Type};
|
||||
|
||||
pub use self::props::*;
|
||||
use syn_serde::{Expr, Pat, Syn, Type};
|
||||
|
||||
pub type Id = Symbol;
|
||||
|
||||
|
@ -160,7 +163,7 @@ pub enum Rsx {
|
|||
Elem(Elem<Rsx>),
|
||||
Code(Expr),
|
||||
Text(String),
|
||||
ForLoop(),
|
||||
ForLoop(Pat, Expr, Vec<Rsx>),
|
||||
|
||||
#[doc(hidden)]
|
||||
_Nonexhaustive,
|
||||
|
@ -195,8 +198,14 @@ impl ToTokens for Rsx {
|
|||
let string = syn::Lit::Str(syn::LitStr::new(string.as_ref(), Span::call_site()));
|
||||
stream.extend(quote!(#string));
|
||||
}
|
||||
Rsx::ForLoop() => {
|
||||
stream.extend(quote!({ for } { / for }));
|
||||
Rsx::ForLoop(pat, expr, inner) => {
|
||||
let expr = syn::Expr::from_adapter(expr);
|
||||
let pat = syn::Pat::from_adapter(pat);
|
||||
let mut inner_stream = TokenStream::new();
|
||||
for rsx in inner {
|
||||
inner_stream.extend(rsx.to_token_stream());
|
||||
}
|
||||
stream.extend(quote!([ for #pat in #expr ] #inner_stream [ / for ]));
|
||||
}
|
||||
Rsx::_Nonexhaustive => unreachable!("should never be constructed"),
|
||||
}
|
||||
|
@ -208,6 +217,7 @@ pub enum TaggedRsx {
|
|||
Elem(Id, Elem<TaggedRsx>),
|
||||
Code(Id, Box<Expr>),
|
||||
Text(Id, String),
|
||||
ForLoop(Id, Pat, Expr, Vec<TaggedRsx>),
|
||||
|
||||
#[doc(hidden)]
|
||||
_Nonexhaustive,
|
||||
|
@ -216,7 +226,10 @@ pub enum TaggedRsx {
|
|||
impl TaggedRsx {
|
||||
pub fn get_id(&self) -> Id {
|
||||
match self {
|
||||
TaggedRsx::Elem(id, _) | TaggedRsx::Code(id, _) | TaggedRsx::Text(id, _) => *id,
|
||||
TaggedRsx::Elem(id, _)
|
||||
| TaggedRsx::Code(id, _)
|
||||
| TaggedRsx::Text(id, _)
|
||||
| TaggedRsx::ForLoop(id, _, _, _) => *id,
|
||||
_ => unimplemented!("tagged rsx"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Utility functions for property checking.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use proptest::{
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// https://github.com/daboross/serde-tuple-vec-map/blob/master/src/lib.rs
|
||||
//! Utility library for converting a map whose keys are not strings to JSON, which requires string keys.
|
||||
//!
|
||||
//! https://github.com/daboross/serde-tuple-vec-map/blob/master/src/lib.rs
|
||||
|
||||
use std::fmt;
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
//! Visitor that traverses a model and generates code.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use petgraph::graphmap::DiGraphMap;
|
||||
use petgraph::visit::Dfs;
|
||||
use petgraph::{dot::Dot, graphmap::DiGraphMap, visit::Dfs};
|
||||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use syn::{Expr, Type};
|
||||
|
@ -11,25 +12,25 @@ use syn_serde::Syn;
|
|||
use crate::model::{Elem, Id, ModelMap, Rsx, TagLhs, TagRhs, TaggedRsx};
|
||||
use crate::Symbol;
|
||||
|
||||
/// A node within the dependency graph.
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub enum DepNode {
|
||||
// This is an attribute on an element
|
||||
// Not read-only
|
||||
/// This is an attribute on an element
|
||||
///
|
||||
/// Not read-only
|
||||
RsxAttr(Symbol, Symbol),
|
||||
// This is a text node (innertext)
|
||||
// These are read-only
|
||||
/// This is a text node (innertext)
|
||||
///
|
||||
/// These are read-only
|
||||
RsxSpan(Symbol),
|
||||
// This is something in the model
|
||||
/// This is something in the model
|
||||
ModelValue(Symbol),
|
||||
/// This is an iterator (for-loop)
|
||||
Iterator(Symbol),
|
||||
}
|
||||
|
||||
impl DepNode {
|
||||
fn gen_update_code(
|
||||
&self,
|
||||
// model_bimap: &BiHashMap<Id, String>,
|
||||
updates: &mut TokenStream,
|
||||
update_func: &mut TokenStream,
|
||||
) {
|
||||
fn gen_update_code(&self, updates: &mut TokenStream, update_func: &mut TokenStream) {
|
||||
match self {
|
||||
DepNode::ModelValue(sym) => {
|
||||
let sym_name = format_ident!("{}", sym.to_string());
|
||||
|
@ -80,6 +81,10 @@ impl Visitor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn impl_code(&self) -> &TokenStream {
|
||||
&self.impl_code
|
||||
}
|
||||
|
||||
pub fn load_model(&mut self, model: &ModelMap) {
|
||||
for (key, (ty, init)) in model {
|
||||
let ty = Syn::from_adapter(&*ty);
|
||||
|
@ -89,28 +94,34 @@ impl Visitor {
|
|||
// self.model.extend(model.clone());
|
||||
}
|
||||
|
||||
pub fn dot(&self) -> Dot<&DependencyGraph> {
|
||||
Dot::new(&self.deps)
|
||||
}
|
||||
|
||||
pub fn make_graph(&mut self, 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));
|
||||
for (lhs, rhs) in attrs {
|
||||
if let TagLhs::Bind(attr) = lhs {
|
||||
if let TagRhs::Text(text) = rhs {
|
||||
let text_sym = Symbol::from(text);
|
||||
if self.model.contains_key(&text_sym) {
|
||||
let from = DepNode::RsxAttr(node_id, Symbol::from(attr));
|
||||
let to = DepNode::ModelValue(text_sym);
|
||||
self.deps.add_edge(from, to, ());
|
||||
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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +147,24 @@ impl Visitor {
|
|||
TaggedRsx::Code(node_id, Box::new(syn_expr.clone().to_adapter()))
|
||||
}
|
||||
Rsx::Text(literal) => TaggedRsx::Text(node_id, literal.clone()),
|
||||
_ => unimplemented!(),
|
||||
// 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) => {
|
||||
let new_inner = self.make_graph(inner.as_ref());
|
||||
|
||||
// figure out which variables in the iterator that it depends on
|
||||
// for example, [for x in y] depends on y.
|
||||
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, ());
|
||||
}
|
||||
TaggedRsx::ForLoop(node_id, pat.clone(), expr.clone(), new_inner)
|
||||
}
|
||||
unknown => unimplemented!("unknown rsx: {:?}", unknown),
|
||||
};
|
||||
new_nodes.push(new_node);
|
||||
}
|
||||
|
@ -178,40 +206,66 @@ impl Visitor {
|
|||
}
|
||||
}
|
||||
self.impl_code.extend(quote! {
|
||||
fn #make_node_id(&self) -> impl enterprise::stdweb::web::INode {
|
||||
/// Rsx::Elem
|
||||
fn #make_node_id(&self) -> enterprise::stdweb::web::Node {
|
||||
use enterprise::stdweb::web::IElement;
|
||||
let el = enterprise::stdweb::web::document().create_element(#tag).unwrap();
|
||||
el.set_attribute("id", #node_str).unwrap();
|
||||
#updates
|
||||
el
|
||||
el.as_node()
|
||||
}
|
||||
});
|
||||
if let Some(inner) = inner {
|
||||
self.gen_code(inner);
|
||||
}
|
||||
names.push(format!("{}", make_node_id));
|
||||
}
|
||||
TaggedRsx::Code(_, _) => {
|
||||
TaggedRsx::Code(_, expr) => {
|
||||
let expr = syn::Expr::from_adapter(expr);
|
||||
let code_id = format_ident!("code_{}", node_str);
|
||||
self.impl_code.extend(quote! {
|
||||
fn #code_id(&self) -> impl std::string::ToString {
|
||||
#expr
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn #make_node_id(&self) -> impl enterprise::stdweb::web::INode {
|
||||
fn #make_node_id(&self) -> enterprise::stdweb::web::Node {
|
||||
use enterprise::stdweb::web::IElement;
|
||||
let el = enterprise::stdweb::web::document().create_element("span").expect("shouldn't fail");
|
||||
el.set_attribute("id", #node_str).unwrap();
|
||||
el
|
||||
el.as_node()
|
||||
}
|
||||
});
|
||||
names.push(format!("{}", make_node_id));
|
||||
}
|
||||
TaggedRsx::Text(_, literal) => {
|
||||
self.impl_code.extend(quote! {
|
||||
#[inline]
|
||||
fn #make_node_id(&self) -> impl enterprise::stdweb::web::INode {
|
||||
enterprise::stdweb::web::document().create_text_node(#literal)
|
||||
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 mut func_calls = TokenStream::new();
|
||||
for name in self.gen_code(&inner) {
|
||||
let name = format_ident!("{}", name);
|
||||
func_calls.extend(quote! { self.#name(); });
|
||||
}
|
||||
self.impl_code.extend(quote! {
|
||||
#[inline]
|
||||
fn #init_loop_id(&self) -> enterprise::stdweb::web::Node {
|
||||
#func_calls
|
||||
}
|
||||
});
|
||||
names.push(format!("{}", init_loop_id));
|
||||
}
|
||||
_ => unimplemented!("gen_code tagged rsx"),
|
||||
}
|
||||
names.push(format!("{}", make_node_id));
|
||||
}
|
||||
names
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ edition = "2018"
|
|||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[[bin]]
|
||||
name = "test"
|
||||
path = "src/tests.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "0.9.5"
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use proc_macro2::{
|
|||
use symbol::Symbol;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Error as SynError, Expr, Lit, Result as SynResult, Token, Type,
|
||||
Error as SynError, Expr, Lit, Pat, Result as SynResult, Token, Type,
|
||||
};
|
||||
use syn_serde::Syn;
|
||||
|
||||
|
@ -152,7 +152,6 @@ impl Visitor {
|
|||
}
|
||||
buf.push(next_token);
|
||||
}
|
||||
println!("model buf: {:?}", buf);
|
||||
|
||||
// probably shouldn't happen?
|
||||
if buf.len() == 0 {
|
||||
|
@ -173,7 +172,6 @@ impl Visitor {
|
|||
|
||||
let mut map = BTreeMap::new();
|
||||
while let Some((name, ty, init, comma)) = single_def()? {
|
||||
println!("single_def => ({}, {:?}, {:?}, {})", name, ty, init, comma);
|
||||
map.insert(name, (ty, init));
|
||||
if !comma {
|
||||
break;
|
||||
|
@ -185,7 +183,7 @@ impl Visitor {
|
|||
fn consume_view(&mut self) -> Result<Vec<Rsx>, ParseError> {
|
||||
enum Container {
|
||||
Tag(String, BTreeMap<TagLhs, TagRhs>),
|
||||
ForLoop(),
|
||||
ForLoop(Pat, Expr),
|
||||
}
|
||||
|
||||
let mut rsx_parser = RsxParser::new(self.0.clone());
|
||||
|
@ -234,12 +232,13 @@ impl Visitor {
|
|||
RsxToken::Str(string) => {
|
||||
result.push(Rsx::Text(string));
|
||||
}
|
||||
RsxToken::OpeningFor() => {
|
||||
tag_stack.push((Container::ForLoop(), result.clone()));
|
||||
RsxToken::OpeningFor(pat, expr) => {
|
||||
tag_stack.push((Container::ForLoop(pat, expr), result.clone()));
|
||||
}
|
||||
RsxToken::ClosingFor => {
|
||||
if let Some((Container::ForLoop(), mut last_result)) = tag_stack.pop() {
|
||||
last_result.push(Rsx::ForLoop());
|
||||
if let Some((Container::ForLoop(pat, expr), mut last_result)) = tag_stack.pop()
|
||||
{
|
||||
last_result.push(Rsx::ForLoop(pat.to_adapter(), expr.to_adapter(), result));
|
||||
result = last_result;
|
||||
} else {
|
||||
return Err(ParseError::ClosedTooFar);
|
||||
|
|
|
@ -161,7 +161,7 @@ impl RsxParser {
|
|||
kw_for: Token![for],
|
||||
pat: Pat,
|
||||
kw_in: Token![in],
|
||||
iter: Expr,
|
||||
expr: Expr,
|
||||
}
|
||||
|
||||
impl Parse for ForLoopHeader {
|
||||
|
@ -170,7 +170,7 @@ impl RsxParser {
|
|||
kw_for: input.parse()?,
|
||||
pat: input.parse()?,
|
||||
kw_in: input.parse()?,
|
||||
iter: input.parse()?,
|
||||
expr: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -179,10 +179,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()))
|
||||
Ok(Some(RsxToken::OpeningFor(for_loop.pat, for_loop.expr)))
|
||||
}
|
||||
Some(TokenTree::Punct(punct)) if punct.as_char() == '/' => {
|
||||
stream.next();
|
||||
// TODO: check that it's actuall closing the for-loop
|
||||
Ok(Some(RsxToken::ClosingFor))
|
||||
}
|
||||
Some(token) => Err(ParseError::UnexpectedToken(token.clone())),
|
||||
|
@ -215,7 +216,7 @@ pub(crate) enum RsxToken {
|
|||
ClosingTag(Symbol),
|
||||
Str(String),
|
||||
Code(Expr),
|
||||
OpeningFor(),
|
||||
OpeningFor(Pat, Expr),
|
||||
ClosingFor,
|
||||
}
|
||||
|
||||
|
|
43
enterprise-macros/src/tests.rs
Normal file
43
enterprise-macros/src/tests.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
#[macro_use]
|
||||
extern crate enterprise_macros;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
use enterprise_compiler::model::Component;
|
||||
use enterprise_compiler::Visitor;
|
||||
|
||||
component! {
|
||||
component TodoMVC {
|
||||
model {
|
||||
value: String = "",
|
||||
todos: List<String> = List::new(),
|
||||
}
|
||||
|
||||
view {
|
||||
<input bind:value="value" on:submit={|| { todos.push(todo); }} />
|
||||
<ul>
|
||||
[for (key, todo) in todos]
|
||||
<li>{todo} <a on:click={|_| { todos.remove(key); }}>"[x]"</a></li>
|
||||
[/for]
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let component: Component = serde_json::from_str(TodoMVC.as_ref()).unwrap();
|
||||
|
||||
let mut visitor = Visitor::new();
|
||||
visitor.load_model(&component.model);
|
||||
let tagged_dom = 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 all_code = enterprise_compiler::build(&component);
|
||||
let mut file = File::create("ouais.rs").unwrap();
|
||||
write!(file, "{}", all_code).unwrap();
|
||||
}
|
|
@ -8,6 +8,7 @@ component! {
|
|||
}
|
||||
|
||||
view {
|
||||
<input bind:value="name" />
|
||||
<input bind:value="name" />
|
||||
"Hello, " {name} "!"
|
||||
}
|
||||
|
|
|
@ -1,18 +1,141 @@
|
|||
#[macro_use]
|
||||
extern crate enterprise;
|
||||
// #[macro_use]
|
||||
// extern crate enterprise;
|
||||
|
||||
enterprise_mod!(helloworld);
|
||||
// enterprise_mod!(helloworld);
|
||||
|
||||
use enterprise::{Backend, Web};
|
||||
// use enterprise::{Backend, Web};
|
||||
|
||||
use crate::helloworld::HelloWorld;
|
||||
// use crate::helloworld::HelloWorld;
|
||||
|
||||
fn main() {
|
||||
stdweb::initialize();
|
||||
// fn main() {
|
||||
// stdweb::initialize();
|
||||
|
||||
let web = Web;
|
||||
let app = HelloWorld::new(&web);
|
||||
web.initialize(app, "app".into());
|
||||
// let web = Web;
|
||||
// let app = HelloWorld::new(&web);
|
||||
// web.initialize(app, "app".into());
|
||||
|
||||
stdweb::event_loop();
|
||||
// stdweb::event_loop();
|
||||
// }
|
||||
|
||||
use stdweb::web::INode;
|
||||
|
||||
pub struct HelloWorld<B> {
|
||||
_b: std::marker::PhantomData<B>,
|
||||
name: std::sync::Arc<enterprise::parking_lot::Mutex<String>>,
|
||||
}
|
||||
impl<B: enterprise::Backend> HelloWorld<B> {
|
||||
pub fn new(_: &B) -> Self {
|
||||
HelloWorld {
|
||||
_b: std::marker::PhantomData::default(),
|
||||
name: std::sync::Arc::new(enterprise::parking_lot::Mutex::new("hello".into())),
|
||||
}
|
||||
}
|
||||
fn make_sym_0(&self) -> B::NodeType {
|
||||
use enterprise::stdweb::web::IElement;
|
||||
let el = enterprise::stdweb::web::document()
|
||||
.create_element("input")
|
||||
.unwrap();
|
||||
el.set_attribute("id", "sym_0").unwrap();
|
||||
let inner_lock_sym_5 = self.name.clone();
|
||||
{
|
||||
use enterprise::stdweb::{unstable::TryFrom, web::IEventTarget};
|
||||
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();
|
||||
{
|
||||
let mut locked = inner_lock_sym_5.lock();
|
||||
*locked = new_value.clone();
|
||||
}
|
||||
{
|
||||
use enterprise::stdweb::web::{INode, INonElementParentNode};
|
||||
if let Some(target) =
|
||||
enterprise::stdweb::web::document().get_element_by_id("sym_3")
|
||||
{
|
||||
target.set_text_content(&new_value.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
el.as_node().clone()
|
||||
}
|
||||
fn make_sym_1(&self) -> B::NodeType {
|
||||
use enterprise::stdweb::web::IElement;
|
||||
let el = enterprise::stdweb::web::document()
|
||||
.create_element("input")
|
||||
.unwrap();
|
||||
el.set_attribute("id", "sym_1").unwrap();
|
||||
let inner_lock_sym_6 = self.name.clone();
|
||||
{
|
||||
use enterprise::stdweb::{unstable::TryFrom, web::IEventTarget};
|
||||
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();
|
||||
{
|
||||
let mut locked = inner_lock_sym_6.lock();
|
||||
*locked = new_value.clone();
|
||||
}
|
||||
{
|
||||
use enterprise::stdweb::web::{INode, INonElementParentNode};
|
||||
if let Some(target) =
|
||||
enterprise::stdweb::web::document().get_element_by_id("sym_3")
|
||||
{
|
||||
target.set_text_content(&new_value.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
el.as_node().clone()
|
||||
}
|
||||
#[inline]
|
||||
fn make_sym_2(&self) -> B::NodeType {
|
||||
enterprise::stdweb::web::document().create_text_node("Hello, ")
|
||||
}
|
||||
#[inline]
|
||||
fn make_sym_3(&self) -> B::NodeType {
|
||||
use enterprise::stdweb::web::IElement;
|
||||
let el = enterprise::stdweb::web::document()
|
||||
.create_element("span")
|
||||
.expect("shouldn't fail");
|
||||
el.set_attribute("id", "sym_3").unwrap();
|
||||
el.as_node().clone()
|
||||
}
|
||||
#[inline]
|
||||
fn make_sym_4(&self) -> B::NodeType {
|
||||
enterprise::stdweb::web::document().create_text_node("!")
|
||||
}
|
||||
}
|
||||
impl<B: enterprise::Backend> enterprise::Component<B> for HelloWorld<B> {
|
||||
fn create(&self, el: &enterprise::stdweb::web::Element) {
|
||||
{
|
||||
use enterprise::stdweb::web::INode;
|
||||
let sub = self.make_sym_0();
|
||||
el.append_child(&sub);
|
||||
}
|
||||
{
|
||||
use enterprise::stdweb::web::INode;
|
||||
let sub = self.make_sym_1();
|
||||
el.append_child(&sub);
|
||||
}
|
||||
{
|
||||
use enterprise::stdweb::web::INode;
|
||||
let sub = self.make_sym_2();
|
||||
el.append_child(&sub);
|
||||
}
|
||||
{
|
||||
use enterprise::stdweb::web::INode;
|
||||
let sub = self.make_sym_3();
|
||||
el.append_child(&sub);
|
||||
}
|
||||
{
|
||||
use enterprise::stdweb::web::INode;
|
||||
let sub = self.make_sym_4();
|
||||
el.append_child(&sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,12 @@ extern crate enterprise_macros;
|
|||
component! {
|
||||
component TodoMVC {
|
||||
model {
|
||||
value: String = "",
|
||||
todos: List<String> = List::new(),
|
||||
}
|
||||
|
||||
view {
|
||||
<input on:submit={|evt| { todos.push(evt.name); }} />
|
||||
<input bind:value="value" on:submit={|| { todos.push(todo); }} />
|
||||
<ul>
|
||||
[for (key, todo) in todos]
|
||||
<li>{todo} <a on:click={|_| { todos.remove(key); }}>"[x]"</a></li>
|
||||
|
|
|
@ -13,4 +13,8 @@ pub trait Backend: Sized {
|
|||
|
||||
/// Initializes the backend with the given component.
|
||||
fn initialize<C: Component<Self>>(&self, _: C, _: Self::InitParams);
|
||||
|
||||
type NodeType: Node<Self>;
|
||||
}
|
||||
|
||||
pub trait Node<B: Backend> {}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use stdweb::web::{document, INonElementParentNode};
|
||||
use crate::backend::Node;
|
||||
use stdweb::web::{document, INonElementParentNode, Node as WebNode};
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::Component;
|
||||
|
@ -12,7 +13,12 @@ impl Backend for Web {
|
|||
fn initialize<C: Component<Self>>(&self, component: C, params: Self::InitParams) {
|
||||
let id = params.as_ref();
|
||||
if let Some(el) = document().get_element_by_id(id) {
|
||||
component.create(&el);
|
||||
// component.render(&el);
|
||||
component.render();
|
||||
}
|
||||
}
|
||||
|
||||
type NodeType = WebNode;
|
||||
}
|
||||
|
||||
impl Node<Web> for WebNode {}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
//! Enterprise is a backend-agnostic framework for developing server-client GUI applications.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub extern crate enterprise_compiler;
|
||||
|
||||
// re-exports
|
||||
|
@ -18,7 +16,9 @@ pub use crate::backend::{Backend, Web};
|
|||
/// Components are the building-blocks of enterprise applications.
|
||||
pub trait Component<B: Backend> {
|
||||
/// TODO: replace this with a real init function.
|
||||
fn create(&self, el: &crate::stdweb::web::Element);
|
||||
// fn create(&self, el: &crate::stdweb::web::Element);
|
||||
|
||||
fn render(&self) -> B::NodeType;
|
||||
}
|
||||
|
||||
/// Declares a mod
|
||||
|
|
|
@ -10,7 +10,6 @@ use symbol::Symbol;
|
|||
/// - O(1) insertion
|
||||
/// - O(1) deletion
|
||||
/// - O(1) lookup
|
||||
/// - O(n) iteration
|
||||
pub struct List<T> {
|
||||
head: Option<NonNull<Node<T>>>,
|
||||
tail: Option<NonNull<Node<T>>>,
|
||||
|
@ -110,6 +109,15 @@ impl<T> List<T> {
|
|||
IntoIter(self.head)
|
||||
}
|
||||
|
||||
/// Gets the item using its key
|
||||
pub fn get(&self, key: &Symbol) -> Option<&T> {
|
||||
if let Some(node) = self.map.get(key) {
|
||||
unsafe { node.as_ref() }.data.as_ref()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the head of the list
|
||||
pub fn head(&self) -> Option<&T> {
|
||||
if let Some(ref node) = self.head {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Standard modules
|
||||
|
||||
mod list;
|
||||
mod widgets;
|
||||
|
||||
pub use self::list::List;
|
||||
pub use self::widgets::*;
|
||||
|
|
28
src/std/widgets/input_box.rs
Normal file
28
src/std/widgets/input_box.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use std::any::TypeId;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::backend::{Backend, Web};
|
||||
use crate::Component;
|
||||
|
||||
thread_local! {
|
||||
static INPUT_BOX: HashMap<TypeId, Box<dyn Fn(String) -> Box<dyn InputBox>>> = HashMap::new();
|
||||
}
|
||||
|
||||
pub trait InputBox {}
|
||||
|
||||
pub fn InputBox<B: 'static + Backend>(name: impl AsRef<str>) -> Option<Box<dyn InputBox>> {
|
||||
let name = name.as_ref();
|
||||
let ty = TypeId::of::<B>();
|
||||
INPUT_BOX.with(move |input_box| {
|
||||
if let Some(func) = input_box.get(&ty) {
|
||||
Some(func(name.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct InputBoxWeb {}
|
||||
|
||||
impl InputBox for InputBoxWeb {}
|
5
src/std/widgets/mod.rs
Normal file
5
src/std/widgets/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod input_box;
|
||||
mod text;
|
||||
|
||||
use self::input_box::*;
|
||||
use self::text::*;
|
17
src/std/widgets/text.rs
Normal file
17
src/std/widgets/text.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use crate::backend::Web;
|
||||
use crate::Component;
|
||||
use stdweb::web::{document, INode, Node};
|
||||
|
||||
pub struct Text(String);
|
||||
|
||||
impl Text {
|
||||
pub fn new(string: impl AsRef<str>) -> Self {
|
||||
Self(string.as_ref().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Web> for Text {
|
||||
fn render(&self) -> Node {
|
||||
document().create_text_node(&self.0).as_node().clone()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue