hello
This commit is contained in:
parent
db512721a4
commit
54a61d9865
4 changed files with 165 additions and 17 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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]]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue