get stuff on screen

This commit is contained in:
Michael Zhang 2020-02-08 15:31:42 -06:00
parent b316508b7f
commit c427485d6e
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
4 changed files with 156 additions and 49 deletions

View file

@ -11,7 +11,9 @@ use proc_macro2::{Span, TokenStream, TokenTree};
use quote::ToTokens; use quote::ToTokens;
use syn::{punctuated::Punctuated, Expr, ExprPath, Ident, Path, PathArguments, PathSegment}; use syn::{punctuated::Punctuated, Expr, ExprPath, Ident, Path, PathArguments, PathSegment};
#[derive(Debug, PartialEq, Eq, Hash)] type Id = String;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum TagLhs { enum TagLhs {
Bind(String), Bind(String),
On(String), On(String),
@ -19,18 +21,32 @@ enum TagLhs {
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct Tag { struct Elem<T> {
tag: String, tag: String,
attrs: HashMap<TagLhs, String>, attrs: HashMap<TagLhs, String>,
inner: Vec<Rsx>, inner: Vec<T>,
} }
#[derive(Debug)] #[derive(Debug)]
enum Rsx { enum Rsx {
Tag(Tag), Elem(Elem<Rsx>),
Code(Expr), Code(Expr),
Text(String), Text(String),
// For(String, String, Vec<Rsx>), }
#[derive(Debug)]
enum TaggedRsx {
Elem(Id, Elem<TaggedRsx>),
Code(Id, Expr),
Text(Id, String),
}
impl TaggedRsx {
pub fn get_id(&self) -> &str {
match self {
TaggedRsx::Elem(id, _) | TaggedRsx::Code(id, _) | TaggedRsx::Text(id, _) => id.as_ref(),
}
}
} }
#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
@ -45,7 +61,7 @@ enum DepActions {
Updates, Updates,
} }
#[derive(Debug)] #[derive(Default, Debug)]
struct DependencyGraph(DiGraphMap<u32, ()>); struct DependencyGraph(DiGraphMap<u32, ()>);
impl DependencyGraph { impl DependencyGraph {
@ -55,21 +71,19 @@ impl DependencyGraph {
} }
} }
#[derive(Debug)] #[derive(Default, Debug)]
struct Visitor { struct Visitor {
idx: u32, idx: u32,
deps: DependencyGraph, deps: DependencyGraph,
wtf: HashMap<u32, DepNode>, wtf: HashMap<u32, DepNode>,
model: HashMap<String, String>, model: HashMap<String, String>,
impl_code: TokenStream,
} }
impl Visitor { impl Visitor {
fn new() -> Visitor { fn new() -> Visitor {
Visitor { Visitor {
idx: 0, ..Default::default()
deps: DependencyGraph::new(),
wtf: HashMap::new(),
model: HashMap::new(),
} }
} }
@ -92,27 +106,81 @@ impl Visitor {
next next
} }
fn visit(&mut self, nodes: &[Rsx]) { fn make_graph(&mut self, nodes: &[Rsx]) -> Vec<TaggedRsx> {
for node in nodes { nodes
let node_id = self.unique_name("node"); .iter()
println!("Visiting {}: {:?}", node_id, node); .map(|node| {
match node { let node_id = self.unique_name("node");
Rsx::Tag(Tag { tag, attrs, inner }) => { match node {
self.visit(inner); Rsx::Elem(Elem { tag, attrs, inner }) => {
} let tag_inner = self.make_graph(&inner);
Rsx::Code(expr) => { TaggedRsx::Elem(
let deps = extract_model_dependencies(expr); node_id,
for dep in deps { Elem {
if self.model.contains_key(&dep) { tag: tag.to_string(),
let from = self.unique_idx(DepNode::ModelValue); attrs: attrs.clone(),
let to = self.unique_idx(DepNode::RsxSpan); inner: tag_inner,
self.deps.0.add_edge(from, to, ()); },
} )
} }
Rsx::Code(expr) => {
let deps = extract_model_dependencies(expr);
for dep in deps {
if self.model.contains_key(&dep) {
let from = self.unique_idx(DepNode::ModelValue);
let to = self.unique_idx(DepNode::RsxSpan);
self.deps.0.add_edge(from, to, ());
}
}
TaggedRsx::Code(node_id, expr.clone())
}
Rsx::Text(literal) => TaggedRsx::Text(node_id, literal.clone()),
}
})
.collect()
}
fn gen_code(&mut self, nodes: &[TaggedRsx]) -> Vec<String> {
let mut names = Vec::new();
for node in nodes {
let node_id = node.get_id();
let make_node_id = format_ident!("make_{}", node_id);
match node {
TaggedRsx::Elem(node_id, Elem { tag, attrs, inner }) => {
self.impl_code.extend(quote! {
fn #make_node_id(&self) -> impl stdweb::web::IElement {
let el = document().create_element(#tag).unwrap();
el
}
});
self.gen_code(&inner);
}
TaggedRsx::Code(node_id, expr) => {
self.impl_code.extend(quote! {
#[inline]
fn #make_node_id(&self) -> impl stdweb::web::IElement {
let el = document().create_element("span").expect("shouldn't fail");
el.set_attribute("id", #node_id);
el
}
});
}
TaggedRsx::Text(node_id, literal) => {
self.impl_code.extend(quote! {
#[inline]
fn #make_node_id(&self) -> impl stdweb::web::IElement {
let el = document().create_element("span").expect("shouldn't fail");
el.set_text_content(#literal);
el
}
});
} }
_ => (), _ => (),
} }
names.push(format!("{}", make_node_id));
} }
names
} }
} }
@ -132,23 +200,23 @@ fn extract_model_dependencies(expr: &Expr) -> HashSet<String> {
#[proc_macro] #[proc_macro]
pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let todomvc_name = "TodoMVC".to_string(); let helloworld_name = "HelloWorld".to_string();
let todomvc_datamodel: HashMap<String, String> = hashmap! { let helloworld_datamodel: HashMap<String, String> = hashmap! {
"name".into() => "String".into(), "name".into() => "String".into(),
}; };
let todomvc_datainit: HashMap<String, String> = hashmap! { let helloworld_datainit: HashMap<String, String> = hashmap! {
"name".into() => "\"world\".into()".into(), "name".into() => "\"world\".into()".into(),
}; };
let todomvc_dom = vec![ let helloworld_dom = vec![
Rsx::Tag(Tag { Rsx::Elem(Elem {
tag: "input".into(), tag: "input".into(),
attrs: hashmap! { attrs: hashmap! {
TagLhs::Bind("value".into()) => "name".into(), TagLhs::Bind("value".into()) => "name".into(),
}, },
..Tag::default() inner: vec![],
}), }),
Rsx::Text("Hello, ".into()), Rsx::Text("Hello, ".into()),
Rsx::Code(syn::parse_str::<Expr>("name").unwrap()), Rsx::Code(syn::parse_str::<Expr>("name").unwrap()),
@ -156,15 +224,16 @@ pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream
]; ];
let mut visitor = Visitor::new(); let mut visitor = Visitor::new();
visitor.load_model(&todomvc_datamodel); visitor.load_model(&helloworld_datamodel);
visitor.visit(&todomvc_dom); let new_dom = visitor.make_graph(&helloworld_dom);
let toplevel_names = visitor.gen_code(&new_dom);
println!("{:?}", visitor); // println!("{:?}", visitor);
println!("DOT:"); println!("DOT:");
let graph: Graph<_, _, _> = visitor.deps.0.clone().into_graph(); let graph: Graph<_, _, _> = visitor.deps.0.clone().into_graph();
println!("{:?}", Dot::new(&graph)); println!("{:?}", Dot::new(&graph));
let name = format_ident!("{}", todomvc_name); let name = format_ident!("{}", helloworld_name);
let mut model = TokenStream::new(); let mut model = TokenStream::new();
let mut init = TokenStream::new(); let mut init = TokenStream::new();
for (name, ty) in visitor.model { for (name, ty) in visitor.model {
@ -173,26 +242,42 @@ pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream
let ty = format_ident!("{}", ty); let ty = format_ident!("{}", ty);
model.extend(quote! { #name : #ty , }); model.extend(quote! { #name : #ty , });
} }
for (name, value) in todomvc_datainit { for (name, value) in helloworld_datainit {
let name = format_ident!("{}", name); let name = format_ident!("{}", name);
let value = syn::parse_str::<Expr>(&value).unwrap(); let value = syn::parse_str::<Expr>(&value).unwrap();
init.extend(quote! { #name : #value , }); init.extend(quote! { #name : #value , });
} }
let impl_code = &visitor.impl_code;
let mut init_el_code = TokenStream::new();
for fn_name in toplevel_names.iter() {
let fn_name = format_ident!("{}", fn_name);
init_el_code.extend(quote! {
let sub = self.#fn_name();
el.append_child(&sub);
});
}
let result = quote! { let result = quote! {
struct #name { struct #name<B> {
_b: std::marker::PhantomData<B>,
#model #model
} }
impl #name { impl<B> #name<B> {
fn new() -> Self { fn new(_: &B) -> Self {
#name { #name {
_b: std::marker::PhantomData::default(),
#init #init
} }
} }
#impl_code
} }
impl crate::Component for #name { impl<B: Backend> crate::Component<B> for #name<B> {
fn initialize(&self, el: &crate::Element) { fn initialize(&self, el: &crate::Element) {
#init_el_code
} }
} }
}; };

5
src/backend.rs Normal file
View file

@ -0,0 +1,5 @@
pub trait Backend {}
pub struct Web;
impl Backend for Web {}

View file

@ -3,26 +3,32 @@ extern crate enterprise_compiler;
#[macro_use] #[macro_use]
extern crate stdweb; extern crate stdweb;
use stdweb::web::{document, Element, INonElementParentNode}; mod backend;
trait Component { use stdweb::web::{document, Element, INode, IElement, INonElementParentNode};
use crate::backend::{Backend, Web};
trait Component<B: Backend> {
fn initialize(&self, element: &Element); fn initialize(&self, element: &Element);
} }
example!(); example!();
fn render<C: Component>(component: &C, id: impl AsRef<str>) { fn render<B: Backend, C: Component<B>>(component: &C, id: impl AsRef<str>) {
let id = id.as_ref(); let id = id.as_ref();
if let Some(el) = document().get_element_by_id(id) { if let Some(el) = document().get_element_by_id(id) {
component.initialize(&el); println!("Rendering...");
} component.initialize(&el);
}
} }
fn main() { fn main() {
stdweb::initialize(); stdweb::initialize();
let todomvc = TodoMVC::new(); let web = Web;
render(&todomvc, ""); let app = HelloWorld::new(&web);
render(&app, "app");
let message = "Hello world!"; let message = "Hello world!";
js! { console.log(@{message}); } js! { console.log(@{message}); }

11
static/index.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>what the Hek</title>
</head>
<body>
<div id="app"></div>
<script src="enterprise.js"></script>
</body>
</html>