Skip to content

Commit

Permalink
Add macro mather
Browse files Browse the repository at this point in the history
  • Loading branch information
vldm committed Nov 16, 2023
1 parent f1bd815 commit 48ba668
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 6 deletions.
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rstml"
description = "Rust templating for XML-based formats (HTML, SVG, MathML) implemented on top of proc-macro::TokenStreams"
version = "0.11.2"
authors = ["vldm <[email protected]>","stoically <[email protected]>"]
authors = ["vldm <[email protected]>", "stoically <[email protected]>"]
keywords = ["syn", "jsx", "rsx", "html", "macro"]
edition = "2018"
repository = "https://github.com/rs-tml/rstml"
Expand All @@ -24,7 +24,7 @@ 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"]}
proc-macro2 = { version = "1.0.47", features = ["span-locations"] }
criterion = "0.4.0"
eyre = "0.6.8"

Expand All @@ -34,10 +34,10 @@ harness = false
path = "benches/bench.rs"

[workspace]
members = [
"examples/html-to-string-macro"
]
members = ["examples/html-to-string-macro"]

[features]
default = ["colors"]
# Hack that parse input two times, using `proc-macro2::fallback` to recover spaces, and persist original spans.
rawtext-stable-hack = []
colors = ["proc-macro2-diagnostics/colors"]
168 changes: 167 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::HashSet, convert::Infallible, fmt::Debug, marker::PhantomData, rc::Rc};

use derive_where::derive_where;
use proc_macro2::TokenStream;
use proc_macro2::{TokenStream, TokenTree};
use syn::{parse::ParseStream, Result};

use crate::{
Expand All @@ -12,6 +12,106 @@ use crate::{
pub type TransformBlockFn = dyn Fn(ParseStream) -> Result<Option<TokenStream>>;
pub type ElementWildcardFn = dyn Fn(&OpenTag, &CloseTag) -> bool;

#[derive(Copy, Debug, Clone)]
enum TokenStreamOperations {
SkipToken(usize),
UnwrapGroup,
}
#[derive(Clone, Debug, Default)]
pub struct MacroPattern {
// macro_call_input: Rc<Fn(&str) -> &str>,
macro_operations: Vec<TokenStreamOperations>,
// macro_call_prefix: TokenStream,
}

impl MacroPattern {
pub fn new() -> Self {
Self::default()
}
pub fn skip_tokens(mut self, num_tokens: usize) -> Self {
if let Some(TokenStreamOperations::SkipToken(ref mut already_skipped)) =
self.macro_operations.last_mut()
{
*already_skipped += num_tokens;
} else {
self.macro_operations
.push(TokenStreamOperations::SkipToken(num_tokens));
}
self
}

pub fn unwrap_group(mut self) -> Self {
self.macro_operations
.push(TokenStreamOperations::UnwrapGroup);
self
}
/// Try to create `RecoverSpacePattern` from token_stream example.
/// Find first occurence of '%%' tokens, and use its position in tokens as
/// marker. Example:
/// original macro:
/// ```
/// html! {some_context, provided, [ can use guards, etc], {<div>}, [other context]};
/// RecoverSpacePattern::from_token_stream(quote!(
/// html! {ident, ident, // can use real idents, or any other
/// [/* can ignore context of auxilary groups */],
/// {%%}, // important part
/// []
/// }
/// ))
/// .unwrap()
/// ```
pub fn from_token_stream(stream: TokenStream) -> Option<Self> {
let mut pattern = MacroPattern::new();
let mut last_token_percent = false;
for tt in stream {
match tt {
TokenTree::Group(group) => {
if let Some(post_res) = Self::from_token_stream(group.stream()) {
pattern = pattern.unwrap_group();
pattern
.macro_operations
.extend_from_slice(&post_res.macro_operations);
return Some(pattern);
}
}
TokenTree::Punct(p) => match (p.as_char(), last_token_percent) {
('%', true) => return Some(pattern),
('%', false) => {
last_token_percent = true;
// ignore skip
continue;
}
(_, true) => {
last_token_percent = false;
// skip previous token
pattern = pattern.skip_tokens(1);
}
(_, false) => {}
},
_ => {}
}
pattern = pattern.skip_tokens(1)
}
None
}

pub fn match_content(&self, source: TokenStream) -> Option<TokenStream> {
let mut stream = source.into_iter();
for op in &self.macro_operations {
match op {
TokenStreamOperations::SkipToken(num) => {
let _ = stream.nth(num - 1);
}
TokenStreamOperations::UnwrapGroup => match stream.next() {
Some(TokenTree::Group(g)) => stream = g.stream().into_iter(),
_ => return None,
},
}
}
Some(stream.collect())
}
}

/// Configures the `Parser` behavior
#[derive_where(Clone)]
pub struct ParserConfig<C = Infallible> {
Expand Down Expand Up @@ -225,3 +325,69 @@ impl<C> ParserConfig<C> {
}
}
}

#[cfg(test)]
mod test {
use quote::quote;

use super::MacroPattern;

#[test]
fn macro_content_matcher() {
let pattern = MacroPattern::from_token_stream(quote! {html!{ctx, other_arg, %%}}).unwrap();
let content = pattern
.match_content(quote! {html!{ctx, div, <div> <Foo/> </div>}})
.unwrap();

assert_eq!(
content.to_string(),
quote! {<div> <Foo/> </div>}.to_string()
);
}

#[test]
fn macro_content_matcher_ignore_single_percent() {
let pattern = MacroPattern::from_token_stream(quote! {html!{ctx, %other_arg, %%}}).unwrap();
// also check that ignore percent inside html input
let content = pattern
.match_content(quote! {html!{ctx, %div, <div>%% <Foo/> </div>}})
.unwrap();

assert_eq!(
content.to_string(),
quote! {<div>%% <Foo/> </div>}.to_string()
);
}

#[test]
fn macro_content_matcher_group() {
// group type does not count
let pattern =
MacroPattern::from_token_stream(quote! {html!{ctx, other_arg, {%%}}}).unwrap();
let content = pattern
.match_content(quote! {html!{ctx, div, [<div> <Foo/> </div>]}})
.unwrap();

assert_eq!(
content.to_string(),
quote! {<div> <Foo/> </div>}.to_string()
);
}

#[test]
fn macro_content_matcher_with_postfix_group() {
// group type does not count
let pattern = MacroPattern::from_token_stream(
quote! {html!{ctx, other_arg, {%%}, any other context}},
)
.unwrap();
let content = pattern
.match_content(quote! {html!{ctx, div, [<div> <Foo/> </div>], foo}})
.unwrap();

assert_eq!(
content.to_string(),
quote! {<div> <Foo/> </div>}.to_string()
);
}
}

0 comments on commit 48ba668

Please sign in to comment.