get stuff on screen
This commit is contained in:
parent
b316508b7f
commit
c427485d6e
4 changed files with 156 additions and 49 deletions
|
@ -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
5
src/backend.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub trait Backend {}
|
||||||
|
|
||||||
|
pub struct Web;
|
||||||
|
|
||||||
|
impl Backend for Web {}
|
20
src/main.rs
20
src/main.rs
|
@ -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
11
static/index.html
Normal 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>
|
Loading…
Reference in a new issue