symbol graph
This commit is contained in:
parent
c427485d6e
commit
028f8805b2
6 changed files with 261 additions and 106 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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"] }
|
||||
|
||||
|
|
|
@ -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,30 +98,19 @@ impl Visitor {
|
|||
}
|
||||
|
||||
fn load_model(&mut self, model: &HashMap<String, String>) {
|
||||
self.model.extend(model.clone());
|
||||
for (key, value) in model {
|
||||
let id = Symbol::gensym();
|
||||
self.model_bimap.insert(id, key.clone());
|
||||
self.model.insert(id, value.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
|
||||
// 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 {
|
||||
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(
|
||||
|
@ -124,30 +123,29 @@ impl Visitor {
|
|||
)
|
||||
}
|
||||
Rsx::Code(expr) => {
|
||||
let deps = extract_model_dependencies(expr);
|
||||
let deps = self.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);
|
||||
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);
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
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> {
|
||||
/// 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));
|
||||
if let Some(id) = self.model_bimap.get_by_right(&ident.to_string()) {
|
||||
result.insert(*id);
|
||||
}
|
||||
// result.insert(format!("{}", ident));
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
119
enterprise-compiler/src/symbol.rs
Normal file
119
enterprise-compiler/src/symbol.rs
Normal 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
|
||||
}
|
|
@ -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};
|
||||
|
||||
|
|
Loading…
Reference in a new issue