Skip to content

Commit

Permalink
chore: Refine visitor api (remove clone/debug bounds)
Browse files Browse the repository at this point in the history
  • Loading branch information
vldm committed Jan 8, 2024
1 parent 08bf083 commit f66915f
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 39 deletions.
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ bench = false
[dependencies]
proc-macro2 = { version = "1.0.47", features = ["span-locations"] }
quote = "1.0.21"
syn = { version = "2.0.15", features = ["visit-mut", "full", "parsing", "extra-traits"] }
syn = { version = "2.0.15", features = [
"visit-mut",
"full",
"parsing",
"extra-traits",
] }
thiserror = "1.0.37"
syn_derive = "0.1.6"
proc-macro2-diagnostics = { version = "0.10", default-features = false }
derive-where = "1.2.5"

[dev-dependencies]
proc-macro2 = { version = "1.0.47", features = ["span-locations"] }
Expand Down
5 changes: 2 additions & 3 deletions examples/html-to-string-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use quote::{quote, quote_spanned, ToTokens};
use rstml::{
node::{Node, NodeAttribute, NodeName},
visitor::{visit_attributes, visit_nodes, Visitor},
Parser, ParserConfig,
Infallible,
Infallible, Parser, ParserConfig,
};
use syn::spanned::Spanned;
// mod escape;
Expand Down Expand Up @@ -61,7 +60,7 @@ impl<'a> Visitor for WalkNodes<'a> {
self.output.static_format.push_str(&node.value_string());
false
}
fn visit_raw_node<OtherC: rstml::node::CustomNode + Clone + std::fmt::Debug>(
fn visit_raw_node<OtherC: rstml::node::CustomNode>(
&mut self,
node: &mut rstml::node::RawText<OtherC>,
) -> bool {
Expand Down
3 changes: 1 addition & 2 deletions rstml-controll-flow/src/extendable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ macro_rules! impl_tuple {
}
}
fn peek(input: ParseStream) -> bool {
dbg!(&input);
$(dbg!($name::peek_element(&input.fork())))||*
$($name::peek_element(&input.fork()))||*
}
}
};
Expand Down
21 changes: 10 additions & 11 deletions src/node/raw_text.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
use std::marker::PhantomData;

use derive_where::derive_where;
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::ToTokens;
use syn::{
parse::ParseStream,
spanned::Spanned,
token::Brace,
LitStr, Token,
};
use syn::{parse::ParseStream, spanned::Spanned, token::Brace, LitStr, Token};

use super::{CustomNode, Infallible, Node};
use crate::recoverable::ParseRecoverable;

use super::Infallible;
use super::{CustomNode, Node};

/// Raw unquoted text
///
/// Internally it is valid `TokenStream` (stream of rust code tokens).
Expand All @@ -30,7 +24,7 @@ use super::{CustomNode, Node};
/// source_text method is not available in `quote!` context, or in context where
/// input is generated by another macro. In still can return default formatting
/// for TokenStream.
#[derive(Clone, Debug)]
#[derive_where(Clone, Debug)]
pub struct RawText<C = Infallible> {
token_stream: TokenStream,
// Span that started before previous token, and after next.
Expand All @@ -54,6 +48,8 @@ impl<C> Default for RawText<C> {
}

impl<C> RawText<C> {
/// Custom node type parameter is used only for parsing, so it can be
/// changed during usage.
pub fn convert_custom<U>(self) -> RawText<U> {
RawText {
token_stream: self.token_stream,
Expand Down Expand Up @@ -188,7 +184,10 @@ impl RawText {
}

impl<C: CustomNode> ParseRecoverable for RawText<C> {
fn parse_recoverable(parser: &mut crate::recoverable::RecoverableContext, input: ParseStream) -> Option<Self> {
fn parse_recoverable(
parser: &mut crate::recoverable::RecoverableContext,
input: ParseStream,
) -> Option<Self> {
let mut token_stream = TokenStream::new();
let any_node = |input: ParseStream| {
input.peek(Token![<])
Expand Down
2 changes: 0 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ impl<C: CustomNode + std::fmt::Debug> Parser<C> {
top_level_nodes += 1;
nodes.push(parsed_node)
}
dbg!(input.cursor().eof());
dbg!(&parser, input);

// its important to skip tokens, to avoid Unexpected tokens errors.
if !input.is_empty() {
Expand Down
186 changes: 166 additions & 20 deletions src/visitor.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::{ fmt::Debug, marker::PhantomData};
use std::marker::PhantomData;

use crate::Infallible;
use super::node::*;
use crate::atoms::{CloseTag, OpenTag};
use crate::{
atoms::{CloseTag, OpenTag},
Infallible,
};

/// Enum that represents the different types with valid Rust code that can be
/// visited using `syn::Visitor`. Usually `syn::Block` or `syn::Expr`.
Expand Down Expand Up @@ -34,10 +36,7 @@ pub trait Visitor {
fn visit_doctype(&mut self, _node: &mut NodeDoctype) -> bool {
true
}
fn visit_raw_node<OtherC: CustomNode + Clone + Debug>(
&mut self,
_node: &mut RawText<OtherC>,
) -> bool {
fn visit_raw_node<OtherC: CustomNode>(&mut self, _node: &mut RawText<OtherC>) -> bool {
true
}
fn visit_custom(&mut self, _node: &mut Self::Custom) -> bool {
Expand Down Expand Up @@ -111,14 +110,87 @@ macro_rules! try_visit {
/// Wrapper for visitor that calls inner visitors.
/// Inner visitor should implement `Visitor` trait and
/// `syn::visit_mut::VisitMut`.
///
/// For regular usecases it is recommended to use `visit_nodes`,
/// `visit_nodes_with_custom_handler` or `visit_attributes` functions.
///
/// But if you need it can be used by calling `visit_*` methods directly.
///
/// Example:
/// ```rust
/// use quote::quote;
/// use rstml::{
/// node::{Node, NodeText},
/// visitor::{Visitor, VisitorWithDefault},
/// Infallible,
/// };
/// use syn::parse_quote;
///
/// struct TestVisitor;
/// impl Visitor for TestVisitor {
/// type Custom = Infallible;
/// fn visit_text_node(&mut self, node: &mut NodeText) -> bool {
/// *node = parse_quote!("modified");
/// true
/// }
/// }
/// impl syn::visit_mut::VisitMut for TestVisitor {}
///
/// let mut visitor = VisitorWithDefault::new(TestVisitor);
///
/// let tokens = quote! {
/// <div>
/// <span>"Some raw text"</span>
/// <span></span>"And text after span"
/// </div>
/// };
/// let mut nodes = rstml::parse2(tokens).unwrap();
/// for node in &mut nodes {
/// visitor.visit_node(node);
/// }
/// let result = quote! {
/// #(#nodes)*
/// };
/// assert_eq!(
/// result.to_string(),
/// quote! {
/// <div>
/// <span>"modified"</span>
/// <span></span>"modified"
/// </div>
/// }
/// .to_string()
/// );
/// ```
pub struct VisitorWithDefault<C: CustomNode, V: Visitor + syn::visit_mut::VisitMut> {
custom: PhantomData<C>,
visitor: V,
custom_handler: Option<Box<dyn FnMut(&mut C) -> bool>>,
}
impl<C, V> VisitorWithDefault<C, V>
where
C: CustomNode,
V: Visitor<Custom = C> + syn::visit_mut::VisitMut,
{
pub fn new(visitor: V) -> Self {
Self {
custom: PhantomData,
visitor,
custom_handler: None,
}
}
pub fn with_custom_handler(visitor: V, handler: Box<dyn FnMut(&mut C) -> bool>) -> Self {
Self {
custom: PhantomData,
visitor,
custom_handler: Some(handler),
}
}
}

impl<C, V> Visitor for VisitorWithDefault<C, V>
where
C: CustomNode + Debug + Clone,
C: CustomNode,
V: Visitor<Custom = C> + syn::visit_mut::VisitMut,
{
type Custom = C;
Expand Down Expand Up @@ -154,18 +226,19 @@ where

self.visit_raw_node(&mut node.value)
}
fn visit_raw_node<OtherC: CustomNode + Clone + Debug>(
&mut self,
node: &mut RawText<OtherC>,
) -> bool {
fn visit_raw_node<OtherC: CustomNode>(&mut self, node: &mut RawText<OtherC>) -> bool {
visit_child!(self.visitor.visit_raw_node(node));

true
}
fn visit_custom(&mut self, node: &mut Self::Custom) -> bool {
visit_child!(self.visitor.visit_custom(node));

true
if let Some(ref mut handler) = &mut self.custom_handler {
handler(node)
} else {
true
}
}
fn visit_text_node(&mut self, node: &mut NodeText) -> bool {
visit_child!(self.visitor.visit_text_node(node));
Expand Down Expand Up @@ -285,28 +358,64 @@ where
true
}
}
/// Visitor entrypoint.
/// Visit nodes in array calling visitor methods.
/// Recursively visit nodes in children, and attributes.
///
/// Return modified visitor back
pub fn visit_nodes<V, C>(nodes: &mut [Node<C>], visitor: V) -> V
where
V: Visitor<Custom = C> + syn::visit_mut::VisitMut,
<V as Visitor>::Custom: CustomNode,
{
let mut visitor = VisitorWithDefault {
custom: PhantomData,
visitor,
custom_handler: None,
};
for node in nodes {
visitor.visit_node(node);
}
visitor.visitor
}

pub fn visit_nodes<V>(nodes: &mut [Node], visitor: V) -> V
/// Visitor entrypoint.
/// Visit nodes in array calling visitor methods.
/// Recursively visit nodes in children, and attributes.
/// Provide custom handler that is used to visit custom nodes.
/// Custom handler should return true if visitor should continue to traverse,
/// and call visitor methods for its children.
///
/// Return modified visitor back
pub fn visit_nodes_with_custom_handler<V, C>(
nodes: &mut [Node<C>],
handler: Box<dyn FnMut(&mut C) -> bool>,
visitor: V,
) -> V
where
V: Visitor<Custom = Infallible> + syn::visit_mut::VisitMut,
V: Visitor<Custom = C> + syn::visit_mut::VisitMut,
<V as Visitor>::Custom: CustomNode,
{
let mut visitor = VisitorWithDefault {
custom: PhantomData,
visitor,
custom_handler: Some(handler),
};
for node in nodes {
visitor.visit_node(node);
}
visitor.visitor
}

/// Visit attributes in array calling visitor methods.
pub fn visit_attributes<V>(attributes: &mut [NodeAttribute], visitor: V) -> V
where
V: Visitor<Custom = Infallible> + syn::visit_mut::VisitMut,
{
let mut visitor = VisitorWithDefault {
custom: PhantomData,
visitor,
custom_handler: None,
};
for attribute in attributes {
visitor.visit_attribute(attribute);
Expand All @@ -316,10 +425,11 @@ where
#[cfg(test)]
mod tests {

use crate::Infallible;
use quote::{quote, ToTokens};
use syn::parse_quote;

use super::*;
use crate::Infallible;
#[test]
fn collect_node_names() {
#[derive(Default)]
Expand Down Expand Up @@ -411,10 +521,7 @@ mod tests {
impl Visitor for TestVisitor {
type Custom = Infallible;

fn visit_raw_node<OtherC: CustomNode + Clone + Debug>(
&mut self,
node: &mut RawText<OtherC>,
) -> bool {
fn visit_raw_node<OtherC: CustomNode>(&mut self, node: &mut RawText<OtherC>) -> bool {
let raw = node.clone().convert_custom::<Infallible>();
self.collected_raw_text.push(raw);
true
Expand Down Expand Up @@ -485,4 +592,43 @@ mod tests {
vec!["Some raw text", "And text after span", "comment"]
);
}

#[test]
fn modify_text_visitor() {
struct TestVisitor;
impl Visitor for TestVisitor {
type Custom = Infallible;
fn visit_text_node(&mut self, node: &mut NodeText) -> bool {
*node = parse_quote!("modified");
true
}
}
impl syn::visit_mut::VisitMut for TestVisitor {}

let mut visitor = VisitorWithDefault::new(TestVisitor);

let tokens = quote! {
<div>
<span>"Some raw text"</span>
<span></span>"And text after span"
</div>
};
let mut nodes = crate::parse2(tokens).unwrap();
for node in &mut nodes {
visitor.visit_node(node);
}
let result = quote! {
#(#nodes)*
};
assert_eq!(
result.to_string(),
quote! {
<div>
<span>"modified"</span>
<span></span>"modified"
</div>
}
.to_string()
);
}
}

0 comments on commit f66915f

Please sign in to comment.