diff --git a/Cargo.lock b/Cargo.lock index 119dc68..eb97cd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,14 @@ name = "base-x" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bimap" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bumpalo" version = "3.1.2" @@ -37,10 +45,13 @@ dependencies = [ name = "enterprise-compiler" version = "0.1.0" dependencies = [ + "bimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.5.0 (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)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -161,6 +172,11 @@ name = "sha1" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "stdweb" version = "0.4.20" @@ -275,6 +291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum base-x 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" +"checksum bimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "783204f24fd7724ea274d327619cfa6a6018047bb0561a68aadff6f56787591b" "checksum bumpalo 3.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5fb8038c1ddc0a5f73787b130f4cc75151e96ed33e417fde765eb5a81e3532f4" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum discard 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" @@ -295,6 +312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" "checksum serde_json 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "21b01d7f0288608a01dca632cf1df859df6fd6ffa885300fc275ce2ba6221953" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" "checksum stdweb 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" "checksum stdweb-derive 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" "checksum stdweb-internal-macros 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" diff --git a/Cargo.toml b/Cargo.toml index 43beb32..cce246c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,9 @@ authors = ["Michael Zhang "] edition = "2018" [workspace] -members = ["enterprise-compiler"] +members = [ + "enterprise-compiler", +] [dependencies] enterprise-compiler = { path = "enterprise-compiler" } diff --git a/enterprise-compiler/Cargo.toml b/enterprise-compiler/Cargo.toml index bd1a100..db2b3a0 100644 --- a/enterprise-compiler/Cargo.toml +++ b/enterprise-compiler/Cargo.toml @@ -8,9 +8,12 @@ edition = "2018" proc-macro = true [dependencies] +bimap = "0.4.0" +lazy_static = "1.4.0" maplit = "1.0.2" -quote = "1.0.2" petgraph = "0.5.0" -syn = { version = "1.0.14", features = ["extra-traits", "full"] } proc-macro2 = "1.0.8" +quote = "1.0.2" +spin = "0.5.2" +syn = { version = "1.0.14", features = ["extra-traits", "full"] } diff --git a/enterprise-compiler/src/lib.rs b/enterprise-compiler/src/lib.rs index 65726c4..f1bb6df 100644 --- a/enterprise-compiler/src/lib.rs +++ b/enterprise-compiler/src/lib.rs @@ -4,14 +4,19 @@ extern crate quote; extern crate maplit; extern crate proc_macro; +mod symbol; + use std::collections::{HashMap, HashSet}; +use bimap::BiHashMap; use petgraph::{dot::Dot, graph::Graph, graphmap::DiGraphMap}; use proc_macro2::{Span, TokenStream, TokenTree}; use quote::ToTokens; use syn::{punctuated::Punctuated, Expr, ExprPath, Ident, Path, PathArguments, PathSegment}; -type Id = String; +use crate::symbol::Symbol; + +type Id = Symbol; #[derive(Clone, Debug, PartialEq, Eq, Hash)] enum TagLhs { @@ -42,18 +47,21 @@ enum TaggedRsx { } impl TaggedRsx { - pub fn get_id(&self) -> &str { + pub fn get_id(&self) -> Id { match self { - TaggedRsx::Elem(id, _) | TaggedRsx::Code(id, _) | TaggedRsx::Text(id, _) => id.as_ref(), + TaggedRsx::Elem(id, _) | TaggedRsx::Code(id, _) | TaggedRsx::Text(id, _) => *id, } } } -#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] enum DepNode { - RsxAttr, - RsxSpan, - ModelValue, + // This is an attribute on an element + RsxAttr(Symbol), + // This is a text node (innertext) + RsxSpan(Symbol), + // This is something in the model + ModelValue(Symbol), } #[derive(Debug)] @@ -62,7 +70,7 @@ enum DepActions { } #[derive(Default, Debug)] -struct DependencyGraph(DiGraphMap); +struct DependencyGraph(DiGraphMap); impl DependencyGraph { fn new() -> Self { @@ -75,9 +83,11 @@ impl DependencyGraph { struct Visitor { idx: u32, deps: DependencyGraph, - wtf: HashMap, - model: HashMap, + model: HashMap, impl_code: TokenStream, + + // symbol maps + model_bimap: BiHashMap, } impl Visitor { @@ -88,66 +98,54 @@ impl Visitor { } fn load_model(&mut self, model: &HashMap) { - self.model.extend(model.clone()); - } - - fn unique_name(&mut self, base: impl AsRef) -> String { - // TODO: normalize the name somehow so it fits in an ident (ex. strip punct) - let base = base.as_ref(); - let next = self.idx; - self.idx += 1; - format!("{}_{}", base, next) - } - - fn unique_idx(&mut self, node: DepNode) -> u32 { - let next = self.idx; - self.idx += 1; - self.wtf.insert(next, node); - next + for (key, value) in model { + let id = Symbol::gensym(); + self.model_bimap.insert(id, key.clone()); + self.model.insert(id, value.clone()); + } + // self.model.extend(model.clone()); } fn make_graph(&mut self, nodes: &[Rsx]) -> Vec { - nodes - .iter() - .map(|node| { - let node_id = self.unique_name("node"); - match node { - Rsx::Elem(Elem { tag, attrs, inner }) => { - let tag_inner = self.make_graph(&inner); - TaggedRsx::Elem( - node_id, - Elem { - tag: tag.to_string(), - attrs: attrs.clone(), - inner: tag_inner, - }, - ) - } - 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()), + let mut new_nodes = Vec::new(); + for node in nodes { + let node_id = Symbol::gensym(); + let new_node = match node { + Rsx::Elem(Elem { tag, attrs, inner }) => { + let tag_inner = self.make_graph(&inner); + TaggedRsx::Elem( + node_id, + Elem { + tag: tag.to_string(), + attrs: attrs.clone(), + inner: tag_inner, + }, + ) } - }) - .collect() + Rsx::Code(expr) => { + let deps = self.extract_model_dependencies(expr); + for dep in deps { + let from = DepNode::ModelValue(dep); + let to = DepNode::RsxSpan(node_id); + self.deps.0.add_edge(from, to, ()); + } + + TaggedRsx::Code(node_id, expr.clone()) + } + Rsx::Text(literal) => TaggedRsx::Text(node_id, literal.clone()), + }; + new_nodes.push(new_node); + } + new_nodes } fn gen_code(&mut self, nodes: &[TaggedRsx]) -> Vec { let mut names = Vec::new(); for node in nodes { - let node_id = node.get_id(); + let node_id = node.get_id().as_str(); let make_node_id = format_ident!("make_{}", node_id); match node { - TaggedRsx::Elem(node_id, Elem { tag, attrs, inner }) => { + TaggedRsx::Elem(_, 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(); @@ -156,7 +154,7 @@ impl Visitor { }); self.gen_code(&inner); } - TaggedRsx::Code(node_id, expr) => { + TaggedRsx::Code(_, expr) => { self.impl_code.extend(quote! { #[inline] fn #make_node_id(&self) -> impl stdweb::web::IElement { @@ -166,7 +164,7 @@ impl Visitor { } }); } - TaggedRsx::Text(node_id, literal) => { + TaggedRsx::Text(_, literal) => { self.impl_code.extend(quote! { #[inline] fn #make_node_id(&self) -> impl stdweb::web::IElement { @@ -182,50 +180,35 @@ impl Visitor { } names } -} -/// This is using a really dumb heuristic -fn extract_model_dependencies(expr: &Expr) -> HashSet { - let tokens = expr.to_token_stream(); - let mut result = HashSet::new(); + /// This is using a really dumb heuristic + fn extract_model_dependencies(&self, expr: &Expr) -> HashSet { + let tokens = expr.to_token_stream(); + let mut result = HashSet::new(); - for token in tokens.into_iter() { - if let TokenTree::Ident(ident) = token { - result.insert(format!("{}", ident)); + for token in tokens.into_iter() { + if let TokenTree::Ident(ident) = token { + if let Some(id) = self.model_bimap.get_by_right(&ident.to_string()) { + result.insert(*id); + } + // result.insert(format!("{}", ident)); + } } + result } - - result } -#[proc_macro] -pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { - let helloworld_name = "HelloWorld".to_string(); - - let helloworld_datamodel: HashMap = hashmap! { - "name".into() => "String".into(), - }; - - let helloworld_datainit: HashMap = hashmap! { - "name".into() => "\"world\".into()".into(), - }; - - let helloworld_dom = vec![ - Rsx::Elem(Elem { - tag: "input".into(), - attrs: hashmap! { - TagLhs::Bind("value".into()) => "name".into(), - }, - inner: vec![], - }), - Rsx::Text("Hello, ".into()), - Rsx::Code(syn::parse_str::("name").unwrap()), - Rsx::Text("!".into()), - ]; +fn process( + name: impl AsRef, + datamodel: &HashMap, + datainit: &HashMap, + dom: &[Rsx], +) -> TokenStream { + let name = name.as_ref(); let mut visitor = Visitor::new(); - visitor.load_model(&helloworld_datamodel); - let new_dom = visitor.make_graph(&helloworld_dom); + visitor.load_model(&datamodel); + let new_dom = visitor.make_graph(&dom); let toplevel_names = visitor.gen_code(&new_dom); // println!("{:?}", visitor); @@ -233,16 +216,16 @@ pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream let graph: Graph<_, _, _> = visitor.deps.0.clone().into_graph(); println!("{:?}", Dot::new(&graph)); - let name = format_ident!("{}", helloworld_name); + let name = format_ident!("{}", name); let mut model = TokenStream::new(); let mut init = TokenStream::new(); - for (name, ty) in visitor.model { + for (name, ty) in datamodel { let name = format_ident!("{}", name); // TODO: parse this into an actual expression tree for Vec let ty = format_ident!("{}", ty); model.extend(quote! { #name : #ty , }); } - for (name, value) in helloworld_datainit { + for (name, value) in datainit { let name = format_ident!("{}", name); let value = syn::parse_str::(&value).unwrap(); init.extend(quote! { #name : #value , }); @@ -258,7 +241,7 @@ pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream }); } - let result = quote! { + quote! { struct #name { _b: std::marker::PhantomData, #model @@ -280,7 +263,37 @@ pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream #init_el_code } } + } +} + +#[proc_macro] +pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + let helloworld_datamodel: HashMap = hashmap! { + "name".into() => "String".into(), }; - result.into() + let helloworld_datainit: HashMap = hashmap! { + "name".into() => "\"world\".into()".into(), + }; + + let helloworld_dom = vec![ + Rsx::Elem(Elem { + tag: "input".into(), + attrs: hashmap! { + TagLhs::Bind("value".into()) => "name".into(), + }, + inner: vec![], + }), + Rsx::Text("Hello, ".into()), + Rsx::Code(syn::parse_str::("name").unwrap()), + Rsx::Text("!".into()), + ]; + + process( + "HelloWorld", + &helloworld_datamodel, + &helloworld_datainit, + &helloworld_dom, + ) + .into() } diff --git a/enterprise-compiler/src/symbol.rs b/enterprise-compiler/src/symbol.rs new file mode 100644 index 0000000..d150718 --- /dev/null +++ b/enterprise-compiler/src/symbol.rs @@ -0,0 +1,119 @@ +// https://github.com/remexre/symbol-rs + +use std::cmp::Ordering; +use std::collections::BTreeSet; +use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use std::mem::{forget, transmute}; +use std::ops::Deref; +use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; + +use lazy_static::lazy_static; +use spin::Mutex; + +lazy_static! { + static ref SYMBOL_HEAP: Mutex> = Mutex::new(BTreeSet::new()); +} + +/// An interned string with O(1) equality. +#[derive(Clone, Copy, Eq, Hash, PartialOrd)] +pub struct Symbol { + s: &'static str, +} + +impl Symbol { + /// Retrieves the address of the backing string. + pub fn addr(self) -> usize { + self.s.as_ptr() as usize + } + + /// Retrieves the string from the Symbol. + pub fn as_str(self) -> &'static str { + self.s + } + + /// Generates a new symbol with a name of the form `G#n`, where `n` is some positive integer. + pub fn gensym() -> Symbol { + lazy_static! { + static ref N: AtomicUsize = AtomicUsize::new(0); + } + + let mut heap = SYMBOL_HEAP.lock(); + let n = loop { + let n = leak_string(format!("sym_{}", N.fetch_add(1, AtomicOrdering::SeqCst))); + if heap.insert(n) { + break n; + } + }; + drop(heap); + + Symbol::from(n) + } +} + +impl Debug for Symbol { + fn fmt(&self, fmt: &mut Formatter) -> FmtResult { + Debug::fmt(self.s, fmt) + } +} + +impl Deref for Symbol { + type Target = str; + fn deref(&self) -> &str { + self.s + } +} + +impl Display for Symbol { + fn fmt(&self, fmt: &mut Formatter) -> FmtResult { + fmt.write_str(self.s) + } +} + +impl> From for Symbol { + fn from(s: S) -> Symbol { + let s = s.as_ref(); + { + let mut heap = SYMBOL_HEAP.lock(); + if heap.get(s).is_none() { + heap.insert(leak_string(s.to_owned())); + } + } + let s = { + let heap = SYMBOL_HEAP.lock(); + heap.get(s).unwrap().clone() + }; + Symbol { s } + } +} + +impl Ord for Symbol { + fn cmp(&self, other: &Self) -> Ordering { + let l = self.addr(); + let r = other.addr(); + l.cmp(&r) + } +} + +impl PartialEq for Symbol { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} + +impl> PartialEq for Symbol { + fn eq(&self, other: &S) -> bool { + self.partial_cmp(&other.as_ref()) == Some(Ordering::Equal) + } +} + +impl> PartialOrd for Symbol { + fn partial_cmp(&self, other: &S) -> Option { + self.s.partial_cmp(other.as_ref()) + } +} + +fn leak_string(s: String) -> &'static str { + let out = unsafe { transmute(&s as &str) }; + forget(s); + out +} diff --git a/src/main.rs b/src/main.rs index d58a32c..7511d60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ extern crate stdweb; mod backend; -use stdweb::web::{document, Element, INode, IElement, INonElementParentNode}; +use stdweb::web::{document, Element, IElement, INode, INonElementParentNode}; use crate::backend::{Backend, Web};