diff --git a/Cargo.lock b/Cargo.lock index d82c2aa..119dc68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,7 +39,9 @@ version = "0.1.0" dependencies = [ "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)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/enterprise-compiler/Cargo.toml b/enterprise-compiler/Cargo.toml index a071f3a..bd1a100 100644 --- a/enterprise-compiler/Cargo.toml +++ b/enterprise-compiler/Cargo.toml @@ -11,3 +11,6 @@ proc-macro = true 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" + diff --git a/enterprise-compiler/src/lib.rs b/enterprise-compiler/src/lib.rs index 692d3ae..e2134bc 100644 --- a/enterprise-compiler/src/lib.rs +++ b/enterprise-compiler/src/lib.rs @@ -1,8 +1,13 @@ -extern crate proc_macro; -#[macro_use] extern crate maplit; #[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)] enum TagLhs { @@ -21,33 +26,171 @@ struct Tag { #[derive(Debug)] enum Rsx { Tag(Tag), - CodeSegment(String), + Code(Expr), Text(String), + + // For(String, String, Vec), +} + +#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +enum DepNode { + RsxAttr, + RsxSpan, + ModelValue, +} + +#[derive(Debug)] +enum DepActions { + Updates, +} + +#[derive(Debug)] +struct DependencyGraph(DiGraphMap); + +impl DependencyGraph { + fn new() -> Self { + let graph = DiGraphMap::new(); + DependencyGraph(graph) + } +} + +#[derive(Debug)] +struct Visitor { + idx: u32, + deps: DependencyGraph, + wtf: HashMap, + model: HashMap, +} + +impl Visitor { + fn new() -> Visitor { + Visitor { + idx: 0, + deps: DependencyGraph::new(), + wtf: HashMap::new(), + model: HashMap::new(), + } + } + + 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 + } + + 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 { + 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] -pub fn example(_: proc_macro::TokenStream) -> proc_macro::TokenStream { - let _todomvc_datamodel: HashMap = hashmap! { - "hello".into() => "String".into(), +pub fn example(input_tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + let todomvc_name = "TodoMVC".to_string(); + + let todomvc_datamodel: HashMap = hashmap! { + "name".into() => "String".into(), }; - let _todomvc_dom = vec![ + let todomvc_dom = vec![ Rsx::Tag(Tag { tag: "input".into(), attrs: hashmap! { - TagLhs::Bind("value".into()) => "hello".into(), - TagLhs::On("keyup".into()) => "addTodo".into(), + TagLhs::Bind("value".into()) => "name".into(), }, - ..Default::default() - }), - Rsx::Tag(Tag { - tag: "div".into(), - ..Default::default() + ..Tag::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 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 + let ty = format_ident!("{}", ty); + model.extend(quote!{ #name : #ty , }); + } let result = quote! { - + struct #name { + #model + } + + impl #name { + + } }; result.into() diff --git a/src/main.rs b/src/main.rs index 1fd5e19..411f3d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,4 +4,4 @@ example!(); fn main() { -} \ No newline at end of file +}