use crate::tag::TagKind; use crate::Tag; use quote::{quote, quote_spanned}; use std::collections::HashMap; use syn::export::Span; use syn::spanned::Spanned; use syn::{Ident, Stmt}; mod braced; mod close_tag; mod open_tag; mod statement; mod text; pub enum NodesToPush<'a> { Stmt(&'a Stmt), TokenStream(&'a Stmt, proc_macro2::TokenStream), } /// Used to parse [`Tag`]s that we've parsed and build a tree of `VirtualNode`s /// /// [`Tag`]: enum.Tag.html pub struct HtmlParser { /// As we parse our macro tokens we'll generate new tokens to return back into the compiler /// when we're done. tokens: Vec, /// Everytime we encounter a new node we'll use the current_node_idx to name it. /// Then we'll increment the current_idx by one. /// This gives every node that we encounter a unique name that we can use to find /// it later when we want to push child nodes into parent nodes current_node_idx: usize, /// The order that we encountered nodes while parsing. node_order: Vec, /// Each time we encounter a new node that could possible be a parent node /// we push it's node index onto the stack. /// /// Text nodes cannot be parent nodes. parent_stack: Vec<(usize, Ident)>, /// Key -> index of the parent node within the HTML tree /// Value -> vector of child node indices parent_to_children: HashMap>, /// The locations of the most recent spans that we parsed. /// Used to determine whether or not to put space around text nodes. recent_span_locations: RecentSpanLocations, /// The last kind of tag that we parsed. /// Used to determine whether or not to put space around text nodes. last_tag_kind: Option, } /// TODO: I've hit a good stopping point... but we can clean these methods up / split them up /// a bit... impl HtmlParser { /// Create a new HtmlParser pub fn new() -> HtmlParser { let mut parent_to_children: HashMap> = HashMap::new(); parent_to_children.insert(0, vec![]); HtmlParser { tokens: vec![], current_node_idx: 0, node_order: vec![], parent_stack: vec![], parent_to_children, recent_span_locations: RecentSpanLocations::default(), last_tag_kind: None, } } /// Generate the tokens for the incoming Tag and update our parser's heuristics that keep /// track of information about what we've parsed. pub fn push_tag(&mut self, tag: &Tag, next_tag: Option<&Tag>) { match tag { Tag::Open { name, attrs, closing_bracket_span, is_self_closing, .. } => { self.parse_open_tag(name, closing_bracket_span, attrs, *is_self_closing); self.last_tag_kind = Some(TagKind::Open); } Tag::Close { name, .. } => { self.parse_close_tag(name); self.last_tag_kind = Some(TagKind::Close); } Tag::Text { text, start_span, end_span, } => { self.parse_text(text, start_span.unwrap(), end_span.unwrap(), next_tag); self.last_tag_kind = Some(TagKind::Text); } Tag::Braced { block, brace_span } => { self.parse_braced(block, brace_span, next_tag); self.last_tag_kind = Some(TagKind::Braced); } }; } /// 1. Pop a node off the stack /// 2. Look up all of it's children in parent_to_children /// 3. Append the children to this node /// 4. Move on to the next node (as in, go back to step 1) pub fn finish(&mut self) -> proc_macro2::TokenStream { let node_order = &mut self.node_order; let parent_to_children = &mut self.parent_to_children; let tokens = &mut self.tokens; if node_order.len() > 1 { for _ in 0..(node_order.len()) { let parent_idx = node_order.pop().unwrap(); // TODO: Figure out how to really use spans let parent_name = Ident::new(format!("node_{}", parent_idx).as_str(), Span::call_site()); let parent_to_children_indices = match parent_to_children.get(&parent_idx) { Some(children) => children, None => continue, }; if parent_to_children_indices.len() > 0 { for child_idx in parent_to_children_indices.iter() { let children = Ident::new(format!("node_{}", child_idx).as_str(), Span::call_site()); let unreachable = quote_spanned!(Span::call_site() => { unreachable!("Non-elements cannot have children"); }); let push_children = quote! { if let Some(ref mut element_node) = #parent_name.as_velement_mut() { element_node.children.extend(#children.into_iter()); } else { #unreachable; } }; tokens.push(push_children); } } } } // Create a virtual node tree let node = quote! { { move |bump| { // __domtree_helper(move |bump| { #(#tokens)* // Root node is always named node_0 node_0 } } }; node } /// Add more tokens to our tokens that we'll eventually return to the compiler. fn push_tokens(&mut self, tokens: proc_macro2::TokenStream) { self.tokens.push(tokens); } /// Set the location of the most recent start tag's ending LineColumn fn set_most_recent_open_tag_end(&mut self, span: Span) { self.recent_span_locations.most_recent_open_tag_end = Some(span); } /// Set the location of the most recent start tag's ending LineColumn fn set_most_recent_block_start(&mut self, span: Span) { self.recent_span_locations.most_recent_block_start = Some(span); } /// Determine whether or not there is any space between the end of the first /// span and the beginning of the second span. /// /// There is space if they are on separate lines or if they have different columns. /// /// html! {
Hello
} <--- no space between end of div and Hello /// /// html! {
Hello
} <--- space between end of div and Hello fn separated_by_whitespace(&self, first_span: &Span, second_span: &Span) -> bool { if first_span.end().line != second_span.end().line { return true; } second_span.start().column - first_span.end().column > 0 } /// Create a new identifier for a VirtualNode and increment our node_idx so that next /// time we call this our node will get a different name. fn new_virtual_node_ident(&mut self, span: Span) -> Ident { let node_name = format!("node_{}", self.current_node_idx); let node_ident = Ident::new(node_name.as_str(), span); // TODO: Increment before creating the new node, not after. // This way the current virtual node ident won't need to do strange subtraction self.current_node_idx += 1; node_ident } /// Get the Ident for the current (last created) virtual node, without incrementing /// the node index. fn current_virtual_node_ident(&self, span: Span) -> Ident { // TODO: Increment before creating the new node, not after. // This way the current virtual node ident won't need to do strange subtraction let node_name = format!("node_{}", self.current_node_idx - 1); Ident::new(node_name.as_str(), span) } /// Generate virtual node tokens for a statement that came from in between braces /// /// examples: /// /// html! {
{ some_var_in_braces }
/// html! {
{ some_other_variable }
fn push_iterable_nodes(&mut self, nodes: NodesToPush) { let node_idx = self.current_node_idx; match nodes { NodesToPush::Stmt(stmt) => { let node_ident = self.new_virtual_node_ident(stmt.span()); self.push_tokens(quote! { let mut #node_ident: IterableNodes = (#stmt).into(); }); } NodesToPush::TokenStream(stmt, tokens) => { let node_ident = self.new_virtual_node_ident(stmt.span()); self.push_tokens(quote! { let mut #node_ident: IterableNodes = #tokens.into(); }); } } let parent_idx = *&self.parent_stack[self.parent_stack.len() - 1].0; self.parent_to_children .get_mut(&parent_idx) .expect("Parent of these iterable nodes") .push(node_idx); self.node_order.push(node_idx); } } /// Keep track of the locations of different kinds of tokens that we encounter. /// /// This helps us determine whether or not to insert space before or after text tokens /// in cases such as: /// /// ```ignore /// html! {
{ Hello World }
/// html! {
{Hello World}
/// ``` #[derive(Default)] struct RecentSpanLocations { most_recent_open_tag_end: Option, most_recent_block_start: Option, } fn is_self_closing(tag: &str) -> bool { crate::validation::self_closing::is_self_closing(tag) } fn is_valid_tag(tag: &str) -> bool { crate::validation::valid_tags::is_valid_tag(tag) }