symbol graph

This commit is contained in:
Michael Zhang 2020-02-08 17:36:17 -06:00
parent c427485d6e
commit 028f8805b2
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
6 changed files with 261 additions and 106 deletions

18
Cargo.lock generated
View file

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

View file

@ -5,7 +5,9 @@ authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018"
[workspace]
members = ["enterprise-compiler"]
members = [
"enterprise-compiler",
]
[dependencies]
enterprise-compiler = { path = "enterprise-compiler" }

View file

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

View file

@ -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<u32, ()>);
struct DependencyGraph(DiGraphMap<DepNode, ()>);
impl DependencyGraph {
fn new() -> Self {
@ -75,9 +83,11 @@ impl DependencyGraph {
struct Visitor {
idx: u32,
deps: DependencyGraph,
wtf: HashMap<u32, DepNode>,
model: HashMap<String, String>,
model: HashMap<Id, String>,
impl_code: TokenStream,
// symbol maps
model_bimap: BiHashMap<Id, String>,
}
impl Visitor {
@ -88,66 +98,54 @@ impl Visitor {
}
fn load_model(&mut self, model: &HashMap<String, String>) {
self.model.extend(model.clone());
}
fn unique_name(&mut self, base: impl AsRef<str>) -> 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<TaggedRsx> {
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<String> {
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<String> {
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<Symbol> {
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<String, String> = hashmap! {
"name".into() => "String".into(),
};
let helloworld_datainit: HashMap<String, String> = 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::<Expr>("name").unwrap()),
Rsx::Text("!".into()),
];
fn process(
name: impl AsRef<str>,
datamodel: &HashMap<String, String>,
datainit: &HashMap<String, String>,
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<T>
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::<Expr>(&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> {
_b: std::marker::PhantomData<B>,
#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<String, String> = hashmap! {
"name".into() => "String".into(),
};
result.into()
let helloworld_datainit: HashMap<String, String> = 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::<Expr>("name").unwrap()),
Rsx::Text("!".into()),
];
process(
"HelloWorld",
&helloworld_datamodel,
&helloworld_datainit,
&helloworld_dom,
)
.into()
}

View file

@ -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<BTreeSet<&'static str>> = 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<S: AsRef<str>> From<S> 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<S: AsRef<str>> PartialEq<S> for Symbol {
fn eq(&self, other: &S) -> bool {
self.partial_cmp(&other.as_ref()) == Some(Ordering::Equal)
}
}
impl<S: AsRef<str>> PartialOrd<S> for Symbol {
fn partial_cmp(&self, other: &S) -> Option<Ordering> {
self.s.partial_cmp(other.as_ref())
}
}
fn leak_string(s: String) -> &'static str {
let out = unsafe { transmute(&s as &str) };
forget(s);
out
}

View file

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