This commit is contained in:
Michael Zhang 2020-02-23 05:11:51 -06:00
parent 0daf13f066
commit fbd3d8ffd2
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
23 changed files with 425 additions and 101 deletions

22
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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();
}

View file

@ -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"),
}
}

View file

@ -1,3 +1,5 @@
//! Utility functions for property checking.
use std::collections::BTreeMap;
use proptest::{

View file

@ -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;

View file

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

View file

@ -7,6 +7,10 @@ edition = "2018"
[lib]
proc-macro = true
[[bin]]
name = "test"
path = "src/tests.rs"
[dev-dependencies]
proptest = "0.9.5"

View file

@ -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);

View file

@ -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,
}

View 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();
}

View file

@ -8,6 +8,7 @@ component! {
}
view {
<input bind:value="name" />
<input bind:value="name" />
"Hello, " {name} "!"
}

View file

@ -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);
}
}
}

View file

@ -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>

View file

View file

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

View file

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

View file

@ -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

View file

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

View file

@ -1,5 +1,7 @@
//! Standard modules
mod list;
mod widgets;
pub use self::list::List;
pub use self::widgets::*;

View 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
View 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
View 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()
}
}