This commit is contained in:
Michael Zhang 2020-02-06 21:55:41 -06:00
parent db512721a4
commit 54a61d9865
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
4 changed files with 165 additions and 17 deletions

2
Cargo.lock generated
View file

@ -39,7 +39,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"maplit 1.0.2 (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)", "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)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]

View file

@ -11,3 +11,6 @@ proc-macro = true
maplit = "1.0.2" maplit = "1.0.2"
quote = "1.0.2" quote = "1.0.2"
petgraph = "0.5.0" petgraph = "0.5.0"
syn = { version = "1.0.14", features = ["extra-traits", "full"] }
proc-macro2 = "1.0.8"

View file

@ -1,8 +1,13 @@
extern crate proc_macro;
#[macro_use] extern crate maplit;
#[macro_use] extern crate quote; #[macro_use] extern crate quote;
#[macro_use] extern crate maplit;
extern crate proc_macro;
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use petgraph::{dot::Dot, graph::Graph, graphmap::DiGraphMap};
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::ToTokens;
use syn::{Expr, ExprPath, Ident, Path, PathSegment, PathArguments, punctuated::Punctuated};
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
enum TagLhs { enum TagLhs {
@ -21,33 +26,171 @@ struct Tag {
#[derive(Debug)] #[derive(Debug)]
enum Rsx { enum Rsx {
Tag(Tag), Tag(Tag),
CodeSegment(String), Code(Expr),
Text(String), Text(String),
// For(String, String, Vec<Rsx>),
}
#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
enum DepNode {
RsxAttr,
RsxSpan,
ModelValue,
}
#[derive(Debug)]
enum DepActions {
Updates,
}
#[derive(Debug)]
struct DependencyGraph(DiGraphMap<u32, ()>);
impl DependencyGraph {
fn new() -> Self {
let graph = DiGraphMap::new();
DependencyGraph(graph)
}
}
#[derive(Debug)]
struct Visitor {
idx: u32,
deps: DependencyGraph,
wtf: HashMap<u32, DepNode>,
model: HashMap<String, String>,
}
impl Visitor {
fn new() -> Visitor {
Visitor {
idx: 0,
deps: DependencyGraph::new(),
wtf: HashMap::new(),
model: HashMap::new(),
}
}
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
}
fn visit(&mut self, nodes: &[Rsx]) {
for node in nodes {
let node_id = self.unique_name("node");
println!("Visiting {}: {:?}", node_id, node);
match node {
Rsx::Tag(Tag { tag, attrs, inner }) => {
self.visit(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, ());
}
}
}
_ => (),
}
}
}
}
/// 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();
for token in tokens.into_iter() {
if let TokenTree::Ident(ident) = token {
result.insert(format!("{}", ident));
}
}
result
} }
#[proc_macro] #[proc_macro]
pub fn example(_: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let _todomvc_datamodel: HashMap<String, String> = hashmap! { let todomvc_name = "TodoMVC".to_string();
"hello".into() => "String".into(),
let todomvc_datamodel: HashMap<String, String> = hashmap! {
"name".into() => "String".into(),
}; };
let _todomvc_dom = vec![ let todomvc_dom = vec![
Rsx::Tag(Tag { Rsx::Tag(Tag {
tag: "input".into(), tag: "input".into(),
attrs: hashmap! { attrs: hashmap! {
TagLhs::Bind("value".into()) => "hello".into(), TagLhs::Bind("value".into()) => "name".into(),
TagLhs::On("keyup".into()) => "addTodo".into(),
}, },
..Default::default() ..Tag::default()
}),
Rsx::Tag(Tag {
tag: "div".into(),
..Default::default()
}), }),
Rsx::Text("Hello, ".into()),
Rsx::Code(Expr::Path(ExprPath {
attrs: vec![],
qself: None,
path: Path {
leading_colon: None,
segments: {
let mut segments = Punctuated::new();
let ident = Ident::new("name", Span::call_site());
let arguments = PathArguments::None;
segments.push(PathSegment {
ident,
arguments,
});
segments
},
},
})),
Rsx::Text("!".into()),
]; ];
let result = quote! { let mut visitor = Visitor::new();
visitor.load_model(&todomvc_datamodel);
visitor.visit(&todomvc_dom);
println!("{:?}", visitor);
println!("DOT:");
let graph: Graph<_, _, _> = visitor.deps.0.clone().into_graph();
println!("{:?}", Dot::new(&graph));
let name = format_ident!("{}", todomvc_name);
let mut model = TokenStream::new();
for (name, ty) in visitor.model {
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 , });
}
let result = quote! {
struct #name {
#model
}
impl #name {
}
}; };
result.into() result.into()